From 846c9d5860a51773194b7fd3661aef2c957a7247 Mon Sep 17 00:00:00 2001 From: Pavel Avgustinov Date: Thu, 30 Aug 2018 10:48:05 +0100 Subject: [PATCH] Migrate Java code to separate QL repo. --- java/ql/src/.project | 12 + java/ql/src/.qlpath | 5 + .../src/.settings/org.eclipse.jdt.core.prefs | 7 + java/ql/src/.vs/VSWorkspaceSettings.json | 8 + .../MissingOverrideAnnotation.java | 15 + .../MissingOverrideAnnotation.qhelp | 54 + .../Declarations/MissingOverrideAnnotation.ql | 30 + .../Declarations/NonFinalImmutableField.qhelp | 35 + .../Declarations/NonFinalImmutableField.ql | 52 + .../Declarations/NonPrivateField.qhelp | 38 + .../Advisory/Declarations/NonPrivateField.ql | 28 + .../AvoidDeprecatedCallableAccess.qhelp | 47 + .../AvoidDeprecatedCallableAccess.ql | 28 + .../ImpossibleJavadocThrows.java | 8 + .../ImpossibleJavadocThrows.qhelp | 46 + .../Documentation/ImpossibleJavadocThrows.ql | 34 + .../ImpossibleJavadocThrowsFix.java | 6 + .../Advisory/Documentation/JavadocCommon.qll | 121 + .../Documentation/MissingJavadocMethods.java | 16 + .../Documentation/MissingJavadocMethods.qhelp | 64 + .../Documentation/MissingJavadocMethods.ql | 16 + .../MissingJavadocParameters.qhelp | 45 + .../Documentation/MissingJavadocParameters.ql | 16 + .../MissingJavadocReturnValues.qhelp | 46 + .../MissingJavadocReturnValues.ql | 16 + .../Documentation/MissingJavadocThrows.qhelp | 49 + .../Documentation/MissingJavadocThrows.ql | 22 + .../Documentation/MissingJavadocTypes.java | 9 + .../Documentation/MissingJavadocTypes.qhelp | 54 + .../Documentation/MissingJavadocTypes.ql | 16 + .../Documentation/SpuriousJavadocParam.java | 52 + .../Documentation/SpuriousJavadocParam.qhelp | 42 + .../Documentation/SpuriousJavadocParam.ql | 33 + .../Java Objects/AvoidCloneMethodAccess.qhelp | 7 + .../Java Objects/AvoidCloneMethodAccess.ql | 23 + .../Java Objects/AvoidCloneOverride.qhelp | 7 + .../Java Objects/AvoidCloneOverride.ql | 25 + .../Java Objects/AvoidCloneableInterface.java | 15 + .../AvoidCloneableInterface.qhelp | 68 + .../Java Objects/AvoidCloneableInterface.ql | 17 + .../Java Objects/AvoidFinalizeOverride.qhelp | 39 + .../Java Objects/AvoidFinalizeOverride.ql | 24 + .../Naming/NamingConventionsCommon.qll | 8 + .../Naming/NamingConventionsConstants.qhelp | 40 + .../Naming/NamingConventionsConstants.ql | 19 + .../Naming/NamingConventionsMethods.qhelp | 40 + .../Naming/NamingConventionsMethods.ql | 16 + .../Naming/NamingConventionsPackages.qhelp | 39 + .../Naming/NamingConventionsPackages.ql | 17 + .../Naming/NamingConventionsRefTypes.qhelp | 40 + .../Naming/NamingConventionsRefTypes.ql | 17 + .../Naming/NamingConventionsVariables.qhelp | 41 + .../Naming/NamingConventionsVariables.ql | 18 + .../Statements/MissingDefaultInSwitch.java | 16 + .../Statements/MissingDefaultInSwitch.qhelp | 47 + .../Statements/MissingDefaultInSwitch.ql | 19 + .../MissingDefaultInSwitchGood.java | 18 + .../Statements/OneStatementPerLine.qhelp | 28 + .../Statements/OneStatementPerLine.ql | 44 + .../Statements/TerminateIfElseIfWithElse.java | 16 + .../TerminateIfElseIfWithElse.qhelp | 48 + .../Statements/TerminateIfElseIfWithElse.ql | 18 + .../TerminateIfElseIfWithElseGood.java | 17 + .../Advisory/Types/GenericsConstructor.qhelp | 9 + .../src/Advisory/Types/GenericsConstructor.ql | 15 + .../Advisory/Types/GenericsReturnType.qhelp | 9 + .../src/Advisory/Types/GenericsReturnType.ql | 17 + .../src/Advisory/Types/GenericsVariable.qhelp | 9 + .../ql/src/Advisory/Types/GenericsVariable.ql | 17 + .../src/Advisory/Types/Generics_Common.java | 6 + .../src/Advisory/Types/Generics_Common.qhelp | 56 + .../Advisory/Types/Generics_CommonGood.java | 6 + java/ql/src/AlertSuppression.ql | 89 + .../Dependencies/MutualDependency.java | 32 + .../Dependencies/MutualDependency.qhelp | 82 + .../Dependencies/MutualDependency.ql | 36 + .../Dependencies/MutualDependencyFix.java | 51 + .../Dependencies/UnusedMavenDependencies.qll | 26 + .../Dependencies/UnusedMavenDependency.qhelp | 35 + .../UnusedMavenDependencyBinary.qhelp | 6 + .../UnusedMavenDependencyBinary.ql | 55 + .../UnusedMavenDependencySource.qhelp | 6 + .../UnusedMavenDependencySource.ql | 34 + .../DeeplyNestedClass.java | 41 + .../DeeplyNestedClass.qhelp | 46 + .../DeeplyNestedClass.ql | 19 + .../DeeplyNestedClassFix.java | 47 + .../FeatureEnvy.java | 22 + .../FeatureEnvy.qhelp | 68 + .../Refactoring Opportunities/FeatureEnvy.ql | 74 + .../HubClasses.qhelp | 53 + .../Refactoring Opportunities/HubClasses.ql | 21 + .../InappropriateIntimacy.qhelp | 40 + .../InappropriateIntimacy.ql | 64 + .../JDK9/JdkInternalAccess.qhelp | 36 + .../Compatibility/JDK9/JdkInternalAccess.ql | 121 + .../src/Compatibility/JDK9/JdkInternals.qll | 1036 + .../JDK9/JdkInternalsReplacement.qll | 54 + .../JDK9/UnderscoreIdentifier.qhelp | 34 + .../JDK9/UnderscoreIdentifier.ql | 33 + .../BlockWithTooManyStatements.qhelp | 40 + .../Complexity/BlockWithTooManyStatements.ql | 24 + java/ql/src/Complexity/ComplexCondition.java | 19 + java/ql/src/Complexity/ComplexCondition.qhelp | 53 + java/ql/src/Complexity/ComplexCondition.ql | 27 + .../src/Complexity/ComplexConditionGood.java | 27 + java/ql/src/DeadCode/DeadClass.java | 30 + java/ql/src/DeadCode/DeadClass.qhelp | 99 + java/ql/src/DeadCode/DeadClass.ql | 35 + java/ql/src/DeadCode/DeadCodeDetails.qhelp | 24 + .../DeadCode/DeadCodeExtraEntryPoints.qhelp | 15 + java/ql/src/DeadCode/DeadCodeReferences.qhelp | 9 + java/ql/src/DeadCode/DeadCodeSummary.qhelp | 17 + java/ql/src/DeadCode/DeadEnumConstant.java | 23 + java/ql/src/DeadCode/DeadEnumConstant.qhelp | 55 + java/ql/src/DeadCode/DeadEnumConstant.ql | 18 + java/ql/src/DeadCode/DeadField.java | 7 + java/ql/src/DeadCode/DeadField.qhelp | 68 + java/ql/src/DeadCode/DeadField.ql | 31 + java/ql/src/DeadCode/DeadFieldSerialized.java | 9 + java/ql/src/DeadCode/DeadFieldWrittenTo.java | 12 + java/ql/src/DeadCode/DeadMethod.java | 23 + java/ql/src/DeadCode/DeadMethod.qhelp | 81 + java/ql/src/DeadCode/DeadMethod.ql | 33 + java/ql/src/DeadCode/DeadMethodTest.java | 12 + java/ql/src/DeadCode/FLinesOfDeadCode.qhelp | 17 + java/ql/src/DeadCode/FLinesOfDeadCode.ql | 50 + java/ql/src/DeadCode/NamespaceClass.java | 25 + java/ql/src/DeadCode/NamespaceClass2.java | 19 + java/ql/src/DeadCode/UselessParameter.java | 3 + java/ql/src/DeadCode/UselessParameter.qhelp | 39 + java/ql/src/DeadCode/UselessParameter.ql | 16 + .../JavaEE/EJB/EjbContainerInterference.qhelp | 36 + .../JavaEE/EJB/EjbContainerInterference.ql | 37 + .../src/Frameworks/JavaEE/EJB/EjbFileIO.qhelp | 35 + .../ql/src/Frameworks/JavaEE/EJB/EjbFileIO.ql | 36 + .../Frameworks/JavaEE/EJB/EjbGraphics.qhelp | 35 + .../src/Frameworks/JavaEE/EJB/EjbGraphics.ql | 32 + .../src/Frameworks/JavaEE/EJB/EjbNative.qhelp | 34 + .../ql/src/Frameworks/JavaEE/EJB/EjbNative.ql | 30 + .../Frameworks/JavaEE/EJB/EjbReflection.qhelp | 37 + .../Frameworks/JavaEE/EJB/EjbReflection.ql | 32 + .../JavaEE/EJB/EjbSecurityConfiguration.qhelp | 35 + .../JavaEE/EJB/EjbSecurityConfiguration.ql | 36 + .../JavaEE/EJB/EjbSerialization.qhelp | 34 + .../Frameworks/JavaEE/EJB/EjbSerialization.ql | 29 + .../JavaEE/EJB/EjbSetSocketOrUrlFactory.qhelp | 36 + .../JavaEE/EJB/EjbSetSocketOrUrlFactory.ql | 33 + .../JavaEE/EJB/EjbSocketAsServer.qhelp | 36 + .../JavaEE/EJB/EjbSocketAsServer.ql | 33 + .../JavaEE/EJB/EjbStaticFieldNonFinal.qhelp | 37 + .../JavaEE/EJB/EjbStaticFieldNonFinal.ql | 38 + .../JavaEE/EJB/EjbSynchronization.qhelp | 35 + .../JavaEE/EJB/EjbSynchronization.ql | 31 + .../src/Frameworks/JavaEE/EJB/EjbThis.qhelp | 33 + java/ql/src/Frameworks/JavaEE/EJB/EjbThis.ql | 34 + .../Frameworks/JavaEE/EJB/EjbThreads.qhelp | 36 + .../src/Frameworks/JavaEE/EJB/EjbThreads.ql | 33 + .../MissingParentBean.qhelp | 44 + .../MissingParentBean.ql | 39 + .../MissingParentBean.xml | 28 + .../MissingParentBeanGood.xml | 24 + .../TooManyBeans.qhelp | 38 + .../Refactoring Opportunities/TooManyBeans.ql | 16 + .../TooManyBeans.xml | 8 + .../Refactoring Opportunities/UnusedBean.java | 12 + .../UnusedBean.qhelp | 60 + .../Refactoring Opportunities/UnusedBean.ql | 233 + .../Refactoring Opportunities/UnusedBean.xml | 6 + .../UselessPropertyOverride.qhelp | 38 + .../UselessPropertyOverride.ql | 27 + .../UselessPropertyOverride.xml | 23 + .../AvoidAutowiring.qhelp | 78 + .../AvoidAutowiring.ql | 16 + .../AvoidAutowiring.xml | 13 + .../DontUseConstructorArgIndex.qhelp | 44 + .../DontUseConstructorArgIndex.ql | 17 + .../DontUseConstructorArgIndex.xml | 13 + .../ImportsFirst.qhelp | 39 + .../ImportsFirst.ql | 22 + .../ImportsFirst.xml | 26 + .../NoBeanDescription.qhelp | 39 + .../NoBeanDescription.ql | 15 + .../NoBeanDescription.xml | 27 + .../ParentShouldNotUseAbstractClass.qhelp | 49 + .../ParentShouldNotUseAbstractClass.ql | 32 + .../ParentShouldNotUseAbstractClass.xml | 14 + .../UseIdInsteadOfName.qhelp | 44 + .../UseIdInsteadOfName.ql | 20 + .../UseIdInsteadOfName.xml | 16 + .../UseLocalRef.qhelp | 58 + .../UseLocalRef.ql | 24 + .../UseLocalRef.xml | 17 + .../UseSetterInjection.java | 22 + .../UseSetterInjection.qhelp | 68 + .../UseSetterInjection.ql | 17 + .../UseSetterInjection.xml | 6 + .../UseSetterInjectionGood.java | 15 + .../UseSetterInjectionGood.xml | 4 + .../UseShortcutForms.qhelp | 46 + .../UseShortcutForms.ql | 58 + .../UseShortcutForms.xml | 38 + .../MissingSetters.java | 9 + .../MissingSetters.qhelp | 43 + .../MissingSetters.ql | 21 + .../MissingSetters.xml | 7 + .../CastThisToTypeParameter.java | 14 + .../CastThisToTypeParameter.qhelp | 29 + .../Language Abuse/CastThisToTypeParameter.ql | 27 + .../CastThisToTypeParameterFix.java | 52 + .../src/Language Abuse/ChainedInstanceof.java | 65 + .../Language Abuse/ChainedInstanceof.qhelp | 83 + .../src/Language Abuse/ChainedInstanceof.ql | 39 + .../Language Abuse/DubiousDowncastOfThis.java | 20 + .../DubiousDowncastOfThis.qhelp | 29 + .../Language Abuse/DubiousDowncastOfThis.ql | 29 + .../Language Abuse/DubiousTypeTestOfThis.java | 20 + .../DubiousTypeTestOfThis.qhelp | 30 + .../Language Abuse/DubiousTypeTestOfThis.ql | 24 + .../ql/src/Language Abuse/EmptyStatement.java | 8 + .../src/Language Abuse/EmptyStatement.qhelp | 39 + java/ql/src/Language Abuse/EmptyStatement.ql | 21 + .../ql/src/Language Abuse/EnumIdentifier.java | 7 + .../src/Language Abuse/EnumIdentifier.qhelp | 40 + java/ql/src/Language Abuse/EnumIdentifier.ql | 21 + .../Language Abuse/ImplementsAnnotation.java | 3 + .../Language Abuse/ImplementsAnnotation.qhelp | 52 + .../Language Abuse/ImplementsAnnotation.ql | 17 + .../ImplementsAnnotationGood.java | 4 + java/ql/src/Language Abuse/IterableClass.qll | 18 + .../src/Language Abuse/IterableIterator.qhelp | 68 + .../ql/src/Language Abuse/IterableIterator.ql | 38 + .../Language Abuse/IterableIteratorBad.java | 26 + .../Language Abuse/IterableIteratorGood1.java | 16 + .../Language Abuse/IterableIteratorGood2.java | 19 + .../src/Language Abuse/IterableOverview.qhelp | 33 + .../MissedTernaryOpportunity.java | 30 + .../MissedTernaryOpportunity.qhelp | 36 + .../MissedTernaryOpportunity.ql | 74 + .../OverridePackagePrivate.java | 29 + .../OverridePackagePrivate.qhelp | 64 + .../Language Abuse/OverridePackagePrivate.ql | 24 + .../TypeVarExtendsFinalType.java | 7 + .../TypeVarExtendsFinalType.qhelp | 48 + .../Language Abuse/TypeVarExtendsFinalType.ql | 21 + .../Language Abuse/TypeVariableHidesType.java | 7 + .../TypeVariableHidesType.qhelp | 48 + .../Language Abuse/TypeVariableHidesType.ql | 39 + .../src/Language Abuse/UselessNullCheck.qhelp | 42 + .../ql/src/Language Abuse/UselessNullCheck.ql | 28 + .../Language Abuse/UselessNullCheckBad.java | 4 + .../src/Language Abuse/UselessTypeTest.java | 11 + .../src/Language Abuse/UselessTypeTest.qhelp | 24 + java/ql/src/Language Abuse/UselessTypeTest.ql | 22 + java/ql/src/Language Abuse/UselessUpcast.java | 25 + .../ql/src/Language Abuse/UselessUpcast.qhelp | 36 + java/ql/src/Language Abuse/UselessUpcast.ql | 68 + .../src/Language Abuse/WrappedIterator.qhelp | 60 + java/ql/src/Language Abuse/WrappedIterator.ql | 49 + .../Language Abuse/WrappedIteratorBad1.java | 19 + .../Language Abuse/WrappedIteratorBad2.java | 18 + .../Language Abuse/WrappedIteratorGood.java | 18 + .../Arithmetic/BadAbsOfRandom.java | 17 + .../Arithmetic/BadAbsOfRandom.qhelp | 51 + .../Likely Bugs/Arithmetic/BadAbsOfRandom.ql | 24 + .../Likely Bugs/Arithmetic/BadCheckOdd.java | 9 + .../Likely Bugs/Arithmetic/BadCheckOdd.qhelp | 45 + .../src/Likely Bugs/Arithmetic/BadCheckOdd.ql | 35 + .../Arithmetic/BadCheckOddGood.java | 9 + .../Likely Bugs/Arithmetic/CondExprTypes.java | 3 + .../Arithmetic/CondExprTypes.qhelp | 49 + .../Likely Bugs/Arithmetic/CondExprTypes.ql | 35 + .../ConstantExpAppearsNonConstant.java | 3 + .../ConstantExpAppearsNonConstant.qhelp | 66 + .../ConstantExpAppearsNonConstant.ql | 84 + .../ConstantExpAppearsNonConstantGood.java | 3 + .../Arithmetic/InformationLoss.qhelp | 51 + .../Likely Bugs/Arithmetic/InformationLoss.ql | 36 + .../Likely Bugs/Arithmetic/IntMultToLong.java | 2 + .../Arithmetic/IntMultToLong.qhelp | 51 + .../Likely Bugs/Arithmetic/IntMultToLong.ql | 55 + .../Arithmetic/IntMultToLongGood.java | 2 + .../Arithmetic/MultiplyRemainder.qhelp | 43 + .../Arithmetic/MultiplyRemainder.ql | 18 + .../Likely Bugs/Arithmetic/OctalLiteral.java | 0 .../Likely Bugs/Arithmetic/OctalLiteral.qhelp | 45 + .../Likely Bugs/Arithmetic/OctalLiteral.ql | 23 + .../Arithmetic/RandomUsedOnce.java | 12 + .../Arithmetic/RandomUsedOnce.qhelp | 57 + .../Likely Bugs/Arithmetic/RandomUsedOnce.ql | 20 + .../WhitespaceContradictsPrecedence.qhelp | 53 + .../WhitespaceContradictsPrecedence.ql | 140 + .../Cloning/MissingCallToSuperClone.qhelp | 50 + .../Cloning/MissingCallToSuperClone.ql | 23 + .../Cloning/MissingCallToSuperCloneBad.java | 26 + .../Cloning/MissingCallToSuperCloneGood.java | 29 + .../Cloning/MissingCloneDetails.qhelp | 36 + .../Cloning/MissingMethodClone.qhelp | 61 + .../Likely Bugs/Cloning/MissingMethodClone.ql | 21 + .../Cloning/MissingMethodCloneBad.java | 46 + .../Cloning/MissingMethodCloneGood.java | 43 + .../Collections/ArrayIndexOutOfBounds.qhelp | 42 + .../Collections/ArrayIndexOutOfBounds.ql | 62 + .../Collections/ArrayIndexOutOfBoundsBad.java | 3 + .../ArrayIndexOutOfBoundsGood.java | 3 + .../Likely Bugs/Collections/Containers.qll | 45 + .../Collections/ContainsTypeMismatch.java | 5 + .../Collections/ContainsTypeMismatch.qhelp | 59 + .../Collections/ContainsTypeMismatch.ql | 75 + .../Collections/ContainsTypeMismatch2.java | 9 + .../Collections/IteratorRemoveMayFail.java | 22 + .../Collections/IteratorRemoveMayFail.qhelp | 49 + .../Collections/IteratorRemoveMayFail.ql | 66 + .../IteratorRemoveMayFailGood.java | 23 + .../Collections/ReadOnlyContainer.java | 8 + .../Collections/ReadOnlyContainer.qhelp | 56 + .../Collections/ReadOnlyContainer.ql | 43 + .../Collections/RemoveTypeMismatch.java | 5 + .../Collections/RemoveTypeMismatch.qhelp | 61 + .../Collections/RemoveTypeMismatch.ql | 70 + .../Collections/RemoveTypeMismatch2.java | 11 + .../Collections/WriteOnlyContainer.java | 12 + .../Collections/WriteOnlyContainer.qhelp | 53 + .../Collections/WriteOnlyContainer.ql | 41 + .../Collections/WriteOnlyContainerGood.java | 15 + .../Comparison/BitwiseSignCheck.java | 4 + .../Comparison/BitwiseSignCheck.qhelp | 46 + .../Comparison/BitwiseSignCheck.ql | 18 + .../Comparison/BitwiseSignCheckGood.java | 4 + .../Comparison/CompareIdenticalValues.java | 23 + .../Comparison/CompareIdenticalValues.qhelp | 57 + .../Comparison/CompareIdenticalValues.ql | 83 + .../Comparison/CovariantCompareTo.java | 19 + .../Comparison/CovariantCompareTo.qhelp | 45 + .../Comparison/CovariantCompareTo.ql | 56 + .../Comparison/CovariantEquals.java | 41 + .../Comparison/CovariantEquals.qhelp | 41 + .../Likely Bugs/Comparison/CovariantEquals.ql | 22 + .../DefineEqualsWhenAddingFields.java | 30 + .../DefineEqualsWhenAddingFields.qhelp | 50 + .../DefineEqualsWhenAddingFields.ql | 93 + .../src/Likely Bugs/Comparison/Equality.qll | 28 + .../Likely Bugs/Comparison/EqualsArray.java | 10 + .../Likely Bugs/Comparison/EqualsArray.qhelp | 52 + .../src/Likely Bugs/Comparison/EqualsArray.ql | 24 + .../Comparison/EqualsUsesInstanceOf.java | 70 + .../Comparison/EqualsUsesInstanceOf.qhelp | 84 + .../Comparison/EqualsUsesInstanceOf.ql | 34 + .../Comparison/HashedButNoHash.java | 24 + .../Comparison/HashedButNoHash.qhelp | 56 + .../Likely Bugs/Comparison/HashedButNoHash.ql | 56 + .../Comparison/HashedButNoHashBad.java | 15 + .../Comparison/IncomparableEquals.java | 10 + .../Comparison/IncomparableEquals.qhelp | 39 + .../Comparison/IncomparableEquals.ql | 63 + .../Comparison/InconsistentCompareTo.java | 10 + .../Comparison/InconsistentCompareTo.qhelp | 53 + .../Comparison/InconsistentCompareTo.ql | 51 + .../Comparison/InconsistentCompareToGood.java | 14 + .../InconsistentEqualsHashCode.java | 10 + .../InconsistentEqualsHashCode.qhelp | 56 + .../Comparison/InconsistentEqualsHashCode.ql | 29 + .../InconsistentEqualsHashCodeGood.java | 21 + .../Comparison/MissingInstanceofInEquals.java | 11 + .../MissingInstanceofInEquals.qhelp | 59 + .../Comparison/MissingInstanceofInEquals.ql | 78 + .../Comparison/NoAssignInBooleanExprs.java | 30 + .../Comparison/NoAssignInBooleanExprs.qhelp | 47 + .../Comparison/NoAssignInBooleanExprs.ql | 38 + .../Comparison/NoComparisonOnFloats.java | 7 + .../Comparison/NoComparisonOnFloats.qhelp | 54 + .../Comparison/NoComparisonOnFloats.ql | 68 + .../Comparison/NoComparisonOnFloatsGood.java | 8 + .../Comparison/ObjectComparison.qhelp | 33 + .../Comparison/ObjectComparison.ql | 42 + .../Likely Bugs/Comparison/RefEqBoxed.java | 3 + .../Likely Bugs/Comparison/RefEqBoxed.qhelp | 45 + .../src/Likely Bugs/Comparison/RefEqBoxed.ql | 20 + .../Likely Bugs/Comparison/RefEqBoxedBad.java | 3 + .../Comparison/StringComparison.java | 7 + .../Comparison/StringComparison.qhelp | 58 + .../Comparison/StringComparison.ql | 64 + .../Comparison/StringComparisonGood.java | 7 + .../Comparison/UselessComparisonTest.java | 9 + .../Comparison/UselessComparisonTest.qhelp | 38 + .../Comparison/UselessComparisonTest.ql | 172 + .../Comparison/UselessComparisonTest.qll | 101 + .../Comparison/WrongNanComparison.qhelp | 32 + .../Comparison/WrongNanComparison.ql | 20 + .../src/Likely Bugs/Concurrency/BusyWait.java | 38 + .../Likely Bugs/Concurrency/BusyWait.qhelp | 62 + .../src/Likely Bugs/Concurrency/BusyWait.ql | 81 + .../Likely Bugs/Concurrency/BusyWaitGood.java | 43 + .../Concurrency/CallsToConditionWait.qhelp | 25 + .../Concurrency/CallsToConditionWait.ql | 34 + .../Concurrency/CallsToRunnableRun.java | 21 + .../Concurrency/CallsToRunnableRun.qhelp | 55 + .../Concurrency/CallsToRunnableRun.ql | 28 + .../Concurrency/CallsToRunnableRunFixed.java | 9 + .../Concurrency/DateFormatThreadUnsafe.java | 22 + .../Concurrency/DateFormatThreadUnsafe.qhelp | 51 + .../Concurrency/DateFormatThreadUnsafe.ql | 24 + .../DateFormatThreadUnsafeGood.java | 22 + .../Concurrency/EmptyRunMethodInThread.java | 8 + .../Concurrency/EmptyRunMethodInThread.qhelp | 57 + .../Concurrency/EmptyRunMethodInThread.ql | 64 + .../EmptyRunMethodInThreadGood.java | 29 + .../Concurrency/FutileSynchOnField.java | 10 + .../Concurrency/FutileSynchOnField.qhelp | 51 + .../Concurrency/FutileSynchOnField.ql | 33 + .../Concurrency/FutileSynchOnFieldGood.java | 11 + .../Concurrency/InconsistentAccess.java | 16 + .../Concurrency/InconsistentAccess.qhelp | 42 + .../Concurrency/InconsistentAccess.ql | 75 + .../Concurrency/LazyInitStaticField.java | 9 + .../Concurrency/LazyInitStaticField.qhelp | 50 + .../Concurrency/LazyInitStaticField.ql | 113 + .../Concurrency/LazyInitStaticFieldGood.java | 11 + .../Concurrency/NonSynchronizedOverride.qhelp | 36 + .../Concurrency/NonSynchronizedOverride.ql | 59 + .../Concurrency/NotifyNotNotifyAll.java | 32 + .../Concurrency/NotifyNotNotifyAll.qhelp | 54 + .../Concurrency/NotifyNotNotifyAll.ql | 30 + .../Concurrency/NotifyWithoutSynch.java | 31 + .../Concurrency/NotifyWithoutSynch.qhelp | 54 + .../Concurrency/NotifyWithoutSynch.ql | 139 + .../Concurrency/PriorityCalls.qhelp | 46 + .../Likely Bugs/Concurrency/PriorityCalls.ql | 30 + .../Concurrency/SleepWithLock.java | 31 + .../Concurrency/SleepWithLock.qhelp | 47 + .../Likely Bugs/Concurrency/SleepWithLock.ql | 25 + .../Concurrency/StartInConstructor.java | 30 + .../Concurrency/StartInConstructor.qhelp | 49 + .../Concurrency/StartInConstructor.ql | 34 + .../Concurrency/StartInConstructorGood.java | 34 + .../Concurrency/SynchOnBoxedType.java | 28 + .../Concurrency/SynchOnBoxedType.qhelp | 60 + .../Concurrency/SynchOnBoxedType.ql | 23 + .../Concurrency/SynchOnBoxedTypeGood.java | 28 + .../Concurrency/SynchSetUnsynchGet.qhelp | 32 + .../Concurrency/SynchSetUnsynchGet.ql | 57 + .../Concurrency/SynchWriteObject.qhelp | 34 + .../Concurrency/SynchWriteObject.ql | 29 + .../Concurrency/UnreleasedLock.java | 10 + .../Concurrency/UnreleasedLock.qhelp | 50 + .../Likely Bugs/Concurrency/UnreleasedLock.ql | 152 + .../Concurrency/WaitOutsideLoop.java | 4 + .../Concurrency/WaitOutsideLoop.qhelp | 39 + .../Concurrency/WaitOutsideLoop.ql | 26 + .../Concurrency/WaitWithTwoLocks.java | 36 + .../Concurrency/WaitWithTwoLocks.qhelp | 45 + .../Concurrency/WaitWithTwoLocks.ql | 29 + .../Concurrency/WaitWithTwoLocksGood.java | 30 + .../Likely Bugs/Concurrency/YieldCalls.qhelp | 42 + .../src/Likely Bugs/Concurrency/YieldCalls.ql | 31 + .../Finalization/NullifiedSuperFinalize.java | 30 + .../Finalization/NullifiedSuperFinalize.qhelp | 58 + .../Finalization/NullifiedSuperFinalize.ql | 22 + .../NullifiedSuperFinalizeGuarded.java | 12 + .../Frameworks/JUnit/BadSuiteMethod.java | 21 + .../Frameworks/JUnit/BadSuiteMethod.qhelp | 41 + .../Frameworks/JUnit/BadSuiteMethod.ql | 25 + .../Frameworks/JUnit/TearDownNoSuper.java | 65 + .../Frameworks/JUnit/TearDownNoSuper.qhelp | 40 + .../Frameworks/JUnit/TearDownNoSuper.ql | 21 + .../Frameworks/JUnit/TestCaseNoTests.java | 28 + .../Frameworks/JUnit/TestCaseNoTests.qhelp | 53 + .../Frameworks/JUnit/TestCaseNoTests.ql | 32 + .../Swing/BadlyOverriddenAdapter.java | 5 + .../Swing/BadlyOverriddenAdapter.qhelp | 97 + .../Swing/BadlyOverriddenAdapter.ql | 39 + .../Swing/BadlyOverriddenAdapterGood.java | 6 + .../Frameworks/Swing/ThreadSafety.java | 19 + .../Frameworks/Swing/ThreadSafety.qhelp | 110 + .../Frameworks/Swing/ThreadSafety.ql | 28 + .../Frameworks/Swing/ThreadSafetyGood.java | 24 + .../I18N/MissingLocaleArgument.java | 14 + .../I18N/MissingLocaleArgument.qhelp | 51 + .../Likely Bugs/I18N/MissingLocaleArgument.ql | 23 + .../Inheritance/NoNonFinalInConstructor.java | 22 + .../Inheritance/NoNonFinalInConstructor.qhelp | 57 + .../Inheritance/NoNonFinalInConstructor.ql | 68 + .../Likely Typos/ConstructorTypo.java | 10 + .../Likely Typos/ConstructorTypo.qhelp | 55 + .../Likely Typos/ConstructorTypo.ql | 20 + .../Likely Typos/ContainerSizeCmpZero.java | 11 + .../Likely Typos/ContainerSizeCmpZero.qhelp | 45 + .../Likely Typos/ContainerSizeCmpZero.ql | 84 + .../ContainerSizeCmpZeroGood.java | 11 + .../Likely Typos/ContradictoryTypeChecks.java | 9 + .../ContradictoryTypeChecks.qhelp | 55 + .../Likely Typos/ContradictoryTypeChecks.ql | 50 + .../ContradictoryTypeChecksGood.java | 9 + .../DangerousNonCircuitLogic.java | 15 + .../DangerousNonCircuitLogic.qhelp | 66 + .../Likely Typos/DangerousNonCircuitLogic.ql | 41 + .../Likely Bugs/Likely Typos/EqualsTypo.java | 14 + .../Likely Bugs/Likely Typos/EqualsTypo.qhelp | 36 + .../Likely Bugs/Likely Typos/EqualsTypo.ql | 20 + .../Likely Typos/FormatStringsOverview.qhelp | 24 + .../Likely Typos/FormatStringsRefs.qhelp | 23 + .../Likely Typos/HashCodeTypo.java | 14 + .../Likely Typos/HashCodeTypo.qhelp | 36 + .../Likely Bugs/Likely Typos/HashCodeTypo.ql | 19 + .../Likely Typos/MissingFormatArg.qhelp | 27 + .../Likely Typos/MissingFormatArg.ql | 21 + .../Likely Typos/MissingFormatArgBad.java | 1 + .../Likely Typos/MissingSpaceTypo.qhelp | 40 + .../Likely Typos/MissingSpaceTypo.ql | 29 + .../Likely Typos/MissingSpaceTypoBad.java | 2 + .../NestedLoopsSameVariable.qhelp | 30 + .../Likely Typos/NestedLoopsSameVariable.ql | 24 + .../Likely Typos/SelfAssignment.java | 13 + .../Likely Typos/SelfAssignment.qhelp | 41 + .../Likely Typos/SelfAssignment.ql | 49 + .../Likely Typos/StringBufferCharInit.java | 18 + .../Likely Typos/StringBufferCharInit.qhelp | 57 + .../Likely Typos/StringBufferCharInit.ql | 30 + .../Likely Typos/ToStringTypo.java | 12 + .../Likely Typos/ToStringTypo.qhelp | 36 + .../Likely Bugs/Likely Typos/ToStringTypo.ql | 18 + .../Likely Typos/UnusedFormatArg.qhelp | 26 + .../Likely Typos/UnusedFormatArg.ql | 33 + .../Likely Typos/UnusedFormatArgBad.java | 1 + .../src/Likely Bugs/Nullness/NullAlways.java | 9 + .../src/Likely Bugs/Nullness/NullAlways.qhelp | 48 + .../ql/src/Likely Bugs/Nullness/NullAlways.ql | 20 + .../Likely Bugs/Nullness/NullExprDeref.java | 3 + .../Likely Bugs/Nullness/NullExprDeref.qhelp | 39 + .../src/Likely Bugs/Nullness/NullExprDeref.ql | 20 + .../src/Likely Bugs/Nullness/NullMaybe.java | 3 + .../src/Likely Bugs/Nullness/NullMaybe.qhelp | 53 + java/ql/src/Likely Bugs/Nullness/NullMaybe.ql | 26 + .../Reflection/AnnotationPresentCheck.java | 17 + .../Reflection/AnnotationPresentCheck.qhelp | 46 + .../Reflection/AnnotationPresentCheck.ql | 26 + .../Reflection/AnnotationPresentCheckFix.java | 18 + .../Resource Leaks/CloseReader.java | 7 + .../Resource Leaks/CloseReader.qhelp | 62 + .../Likely Bugs/Resource Leaks/CloseReader.ql | 40 + .../Resource Leaks/CloseReaderGood.java | 14 + .../Resource Leaks/CloseReaderNested.java | 16 + .../Resource Leaks/CloseReaderNestedGood.java | 17 + .../Likely Bugs/Resource Leaks/CloseSql.java | 9 + .../Likely Bugs/Resource Leaks/CloseSql.qhelp | 51 + .../Likely Bugs/Resource Leaks/CloseSql.ql | 21 + .../Resource Leaks/CloseSqlGood.java | 10 + .../Likely Bugs/Resource Leaks/CloseType.qll | 321 + .../Resource Leaks/CloseWriter.java | 7 + .../Resource Leaks/CloseWriter.qhelp | 61 + .../Likely Bugs/Resource Leaks/CloseWriter.ql | 38 + .../Resource Leaks/CloseWriterGood.java | 14 + .../Resource Leaks/CloseWriterNested.java | 16 + .../Resource Leaks/CloseWriterNestedGood.java | 17 + .../IncorrectSerialVersionUID.java | 11 + .../IncorrectSerialVersionUID.qhelp | 42 + .../IncorrectSerialVersionUID.ql | 24 + .../IncorrectSerializableMethods.java | 25 + .../IncorrectSerializableMethods.qhelp | 47 + .../IncorrectSerializableMethods.ql | 22 + .../IncorrectSerializableMethodsSig.java | 4 + ...issingVoidConstructorOnExternalizable.java | 37 + ...ssingVoidConstructorOnExternalizable.qhelp | 49 + .../MissingVoidConstructorOnExternalizable.ql | 24 + ...MissingVoidConstructorsOnSerializable.java | 42 + ...issingVoidConstructorsOnSerializable.qhelp | 51 + .../MissingVoidConstructorsOnSerializable.ql | 30 + .../NonSerializableComparator.java | 16 + .../NonSerializableComparator.qhelp | 57 + .../NonSerializableComparator.ql | 50 + .../Serialization/NonSerializableField.java | 27 + .../Serialization/NonSerializableField.qhelp | 76 + .../Serialization/NonSerializableField.ql | 98 + .../NonSerializableFieldTooGeneral.java | 29 + .../NonSerializableInnerClass.java | 33 + .../NonSerializableInnerClass.qhelp | 65 + .../NonSerializableInnerClass.ql | 98 + .../Serialization/ReadResolveObject.java | 40 + .../Serialization/ReadResolveObject.qhelp | 65 + .../Serialization/ReadResolveObject.ql | 25 + .../TransientNotSerializable.java | 12 + .../TransientNotSerializable.qhelp | 48 + .../Serialization/TransientNotSerializable.ql | 20 + .../src/Likely Bugs/Statements/Chaining.qll | 63 + .../Likely Bugs/Statements/EmptyBlock.java | 10 + .../Likely Bugs/Statements/EmptyBlock.qhelp | 52 + .../src/Likely Bugs/Statements/EmptyBlock.ql | 60 + .../Statements/EmptySynchronizedBlock.qhelp | 36 + .../Statements/EmptySynchronizedBlock.ql | 19 + .../Statements/ImpossibleCast.qhelp | 43 + .../Likely Bugs/Statements/ImpossibleCast.ql | 112 + .../Statements/InconsistentCallOnResult.java | 12 + .../Statements/InconsistentCallOnResult.qhelp | 32 + .../Statements/InconsistentCallOnResult.ql | 109 + .../Statements/MissingEnumInSwitch.java | 15 + .../Statements/MissingEnumInSwitch.qhelp | 47 + .../Statements/MissingEnumInSwitch.ql | 22 + .../Statements/PartiallyMaskedCatch.java | 11 + .../Statements/PartiallyMaskedCatch.qhelp | 68 + .../Statements/PartiallyMaskedCatch.ql | 110 + .../Statements/ReturnValueIgnored.java | 3 + .../Statements/ReturnValueIgnored.qhelp | 41 + .../Statements/ReturnValueIgnored.ql | 111 + .../StaticFieldWrittenByInstance.java | 22 + .../StaticFieldWrittenByInstance.qhelp | 55 + .../StaticFieldWrittenByInstance.ql | 24 + .../src/Likely Bugs/Statements/UseBraces.java | 13 + .../Likely Bugs/Statements/UseBraces.qhelp | 68 + .../src/Likely Bugs/Statements/UseBraces.ql | 128 + .../Likely Bugs/Statements/UseBraces2.java | 9 + .../Likely Bugs/Statements/UseBracesGood.java | 12 + .../Termination/ConstantLoopCondition.qhelp | 48 + .../Termination/ConstantLoopCondition.ql | 82 + .../Termination/ConstantLoopConditionBad.java | 12 + .../ConstantLoopConditionGood.java | 12 + .../Likely Bugs/Termination/SpinOnField.java | 18 + .../Likely Bugs/Termination/SpinOnField.qhelp | 46 + .../Likely Bugs/Termination/SpinOnField.ql | 68 + java/ql/src/META-INF/MANIFEST.MF | 9 + .../src/Metrics/Authors/AuthorsPerFile.qhelp | 39 + java/ql/src/Metrics/Authors/AuthorsPerFile.ql | 19 + .../Callables/CCyclomaticComplexity.java | 15 + .../Callables/CCyclomaticComplexity.qhelp | 59 + .../Callables/CCyclomaticComplexity.ql | 18 + .../CCyclomaticComplexity_ControlFlow.gv | 11 + .../CCyclomaticComplexity_ControlFlow.png | Bin 0 -> 3010 bytes .../src/Metrics/Callables/CLinesOfCode.qhelp | 38 + java/ql/src/Metrics/Callables/CLinesOfCode.ql | 17 + .../Metrics/Callables/CLinesOfComment.qhelp | 71 + .../src/Metrics/Callables/CLinesOfComment.ql | 17 + .../Metrics/Callables/CNumberOfCalls.qhelp | 72 + .../src/Metrics/Callables/CNumberOfCalls.ql | 19 + .../Callables/CNumberOfParameters.qhelp | 89 + .../Metrics/Callables/CNumberOfParameters.ql | 18 + .../Callables/CNumberOfParameters1.java | 6 + .../Callables/CNumberOfParameters1Good.java | 10 + .../Callables/CNumberOfParameters2.java | 20 + .../Callables/CNumberOfParameters2Good.java | 15 + .../Callables/CNumberOfStatements.qhelp | 42 + .../Metrics/Callables/CNumberOfStatements.ql | 17 + .../Callables/StatementNestingDepth.java | 11 + .../Callables/StatementNestingDepth.qhelp | 56 + .../Callables/StatementNestingDepth.ql | 42 + .../Callables/StatementNestingDepthGood.java | 14 + .../Dependencies/ExternalDependencies.ql | 34 + .../ExternalDependenciesSourceLinks.ql | 21 + .../Metrics/Files/DuplicationProblems.qhelp | 17 + .../src/Metrics/Files/FAfferentCoupling.qhelp | 58 + .../ql/src/Metrics/Files/FAfferentCoupling.ql | 22 + java/ql/src/Metrics/Files/FCommentRatio.qhelp | 6 + java/ql/src/Metrics/Files/FCommentRatio.ql | 20 + .../Metrics/Files/FCyclomaticComplexity.java | 21 + .../Metrics/Files/FCyclomaticComplexity.qhelp | 77 + .../Metrics/Files/FCyclomaticComplexity.ql | 19 + .../FCyclomaticComplexity_ControlFlow.gv | 11 + .../FCyclomaticComplexity_ControlFlow.png | Bin 0 -> 2668 bytes .../src/Metrics/Files/FEfferentCoupling.qhelp | 85 + .../ql/src/Metrics/Files/FEfferentCoupling.ql | 22 + .../Files/FEfferentCoupling_SplitAfter.gv | 18 + .../Files/FEfferentCoupling_SplitAfter.png | Bin 0 -> 3449 bytes .../Files/FEfferentCoupling_SplitBefore.gv | 16 + .../Files/FEfferentCoupling_SplitBefore.png | Bin 0 -> 3586 bytes java/ql/src/Metrics/Files/FLines.ql | 15 + java/ql/src/Metrics/Files/FLinesOfCode.qhelp | 40 + java/ql/src/Metrics/Files/FLinesOfCode.ql | 18 + .../src/Metrics/Files/FLinesOfComment.qhelp | 55 + java/ql/src/Metrics/Files/FLinesOfComment.ql | 18 + .../Metrics/Files/FLinesOfCommentedCode.qhelp | 7 + .../Metrics/Files/FLinesOfCommentedCode.ql | 19 + .../Files/FLinesOfDuplicatedCode.qhelp | 6 + .../Metrics/Files/FLinesOfDuplicatedCode.ql | 25 + .../Metrics/Files/FLinesOfSimilarCode.qhelp | 31 + .../src/Metrics/Files/FLinesOfSimilarCode.ql | 22 + .../src/Metrics/Files/FNumberOfClasses.qhelp | 108 + java/ql/src/Metrics/Files/FNumberOfClasses.ql | 16 + .../Metrics/Files/FNumberOfInterfaces.qhelp | 50 + .../src/Metrics/Files/FNumberOfInterfaces.ql | 16 + .../ql/src/Metrics/Files/FNumberOfTests.qhelp | 57 + java/ql/src/Metrics/Files/FNumberOfTests.ql | 17 + .../Metrics/Files/FSelfContainedness.qhelp | 71 + .../src/Metrics/Files/FSelfContainedness.ql | 29 + java/ql/src/Metrics/History/HChurn.qhelp | 42 + java/ql/src/Metrics/History/HChurn.ql | 19 + java/ql/src/Metrics/History/HLinesAdded.qhelp | 6 + java/ql/src/Metrics/History/HLinesAdded.ql | 16 + .../src/Metrics/History/HLinesDeleted.qhelp | 6 + java/ql/src/Metrics/History/HLinesDeleted.ql | 16 + .../Metrics/History/HNumberOfAuthors.qhelp | 48 + .../src/Metrics/History/HNumberOfAuthors.ql | 14 + .../Metrics/History/HNumberOfChanges.qhelp | 30 + .../src/Metrics/History/HNumberOfChanges.ql | 14 + .../History/HNumberOfRecentChanges.qhelp | 63 + .../Metrics/History/HNumberOfRecentChanges.ql | 20 + .../Internal/CallableDisplayStrings.ql | 17 + .../src/Metrics/Internal/CallableExtents.ql | 12 + .../Metrics/Internal/CallableSourceLinks.ql | 11 + java/ql/src/Metrics/Internal/Extents.qll | 62 + .../Metrics/Internal/ReftypeDisplayStrings.ql | 17 + .../ql/src/Metrics/Internal/ReftypeExtents.ql | 12 + .../Metrics/Internal/ReftypeSourceLinks.ql | 11 + .../Metrics/RefTypes/TAfferentCoupling.qhelp | 80 + .../src/Metrics/RefTypes/TAfferentCoupling.ql | 17 + .../Metrics/RefTypes/TEfferentCoupling.java | 13 + .../Metrics/RefTypes/TEfferentCoupling.qhelp | 62 + .../src/Metrics/RefTypes/TEfferentCoupling.ql | 18 + .../RefTypes/TEfferentCouplingGood.java | 15 + .../RefTypes/TEfferentSourceCoupling.qhelp | 92 + .../RefTypes/TEfferentSourceCoupling.ql | 18 + .../Metrics/RefTypes/TInheritanceDepth.qhelp | 103 + .../src/Metrics/RefTypes/TInheritanceDepth.ql | 17 + ...heritanceDepth_MergeIntoSuperclassAfter.gv | 9 + ...eritanceDepth_MergeIntoSuperclassAfter.png | Bin 0 -> 926 bytes ...eritanceDepth_MergeIntoSuperclassBefore.gv | 10 + ...ritanceDepth_MergeIntoSuperclassBefore.png | Bin 0 -> 981 bytes .../TInheritanceDepth_UseComponentsAfter.gv | 17 + .../TInheritanceDepth_UseComponentsAfter.png | Bin 0 -> 6973 bytes .../TInheritanceDepth_UseComponentsBefore.gv | 17 + .../TInheritanceDepth_UseComponentsBefore.png | Bin 0 -> 4056 bytes .../Metrics/RefTypes/TLackOfCohesionCK.qhelp | 69 + .../src/Metrics/RefTypes/TLackOfCohesionCK.ql | 17 + .../Metrics/RefTypes/TLackOfCohesionHS.qhelp | 77 + .../src/Metrics/RefTypes/TLackOfCohesionHS.ql | 16 + .../src/Metrics/RefTypes/TLinesOfCode.qhelp | 52 + java/ql/src/Metrics/RefTypes/TLinesOfCode.ql | 16 + .../Metrics/RefTypes/TLinesOfComment.qhelp | 55 + .../src/Metrics/RefTypes/TLinesOfComment.ql | 17 + .../Metrics/RefTypes/TNumberOfCallables.qhelp | 102 + .../Metrics/RefTypes/TNumberOfCallables.ql | 16 + .../src/Metrics/RefTypes/TNumberOfFields.java | 10 + .../Metrics/RefTypes/TNumberOfFields.qhelp | 59 + .../src/Metrics/RefTypes/TNumberOfFields.ql | 17 + .../Metrics/RefTypes/TNumberOfFieldsGood.java | 20 + .../RefTypes/TNumberOfStatements.qhelp | 63 + .../Metrics/RefTypes/TNumberOfStatements.ql | 17 + .../RefTypes/TPercentageOfComments.qhelp | 55 + .../Metrics/RefTypes/TPercentageOfComments.ql | 19 + .../RefTypes/TPercentageOfComplexCode.qhelp | 43 + .../RefTypes/TPercentageOfComplexCode.ql | 28 + java/ql/src/Metrics/RefTypes/TResponse.qhelp | 41 + java/ql/src/Metrics/RefTypes/TResponse.ql | 18 + .../Metrics/RefTypes/TSelfContainedness.qhelp | 71 + .../Metrics/RefTypes/TSelfContainedness.ql | 17 + java/ql/src/Metrics/RefTypes/TSizeOfAPI.qhelp | 98 + java/ql/src/Metrics/RefTypes/TSizeOfAPI.ql | 19 + .../RefTypes/TSpecialisationIndex.java | 40 + .../RefTypes/TSpecialisationIndex.qhelp | 67 + .../Metrics/RefTypes/TSpecialisationIndex.ql | 19 + .../RefTypes/TSpecialisationIndexGood.java | 33 + java/ql/src/Metrics/queries.xml | 1 + .../src/Performance/ConcatenationInLoops.java | 18 + .../Performance/ConcatenationInLoops.qhelp | 57 + .../src/Performance/ConcatenationInLoops.ql | 69 + .../InefficientEmptyStringTest.java | 17 + .../InefficientEmptyStringTest.qhelp | 55 + .../Performance/InefficientEmptyStringTest.ql | 21 + .../InefficientKeySetIterator.java | 25 + .../InefficientKeySetIterator.qhelp | 47 + .../Performance/InefficientKeySetIterator.ql | 63 + .../Performance/InefficientOutputStream.qhelp | 64 + .../Performance/InefficientOutputStream.ql | 39 + .../InefficientOutputStreamBad.java | 27 + .../InefficientOutputStreamGood.java | 32 + .../InefficientPrimConstructor.java | 18 + .../InefficientPrimConstructor.qhelp | 62 + .../Performance/InefficientPrimConstructor.ql | 18 + .../src/Performance/InefficientToArray.java | 28 + .../src/Performance/InefficientToArray.qhelp | 61 + java/ql/src/Performance/InefficientToArray.ql | 53 + .../Performance/InnerClassCouldBeStatic.qhelp | 64 + .../Performance/InnerClassCouldBeStatic.ql | 145 + java/ql/src/Performance/NewStringString.java | 10 + java/ql/src/Performance/NewStringString.qhelp | 47 + java/ql/src/Performance/NewStringString.ql | 18 + .../src/Security/CWE/CWE-022/PathsCommon.qll | 79 + .../src/Security/CWE/CWE-022/TaintedPath.java | 24 + .../Security/CWE/CWE-022/TaintedPath.qhelp | 46 + .../src/Security/CWE/CWE-022/TaintedPath.ql | 33 + .../CWE/CWE-022/TaintedPathLocal.qhelp | 5 + .../Security/CWE/CWE-022/TaintedPathLocal.ql | 29 + .../src/Security/CWE/CWE-078/ExecCommon.qll | 20 + .../Security/CWE/CWE-078/ExecRelative.java | 12 + .../Security/CWE/CWE-078/ExecRelative.qhelp | 37 + .../src/Security/CWE/CWE-078/ExecRelative.ql | 26 + .../src/Security/CWE/CWE-078/ExecTainted.java | 9 + .../Security/CWE/CWE-078/ExecTainted.qhelp | 47 + .../src/Security/CWE/CWE-078/ExecTainted.ql | 21 + .../CWE/CWE-078/ExecTaintedLocal.qhelp | 5 + .../Security/CWE/CWE-078/ExecTaintedLocal.ql | 27 + .../Security/CWE/CWE-078/ExecUnescaped.java | 19 + .../Security/CWE/CWE-078/ExecUnescaped.qhelp | 48 + .../src/Security/CWE/CWE-078/ExecUnescaped.ql | 48 + java/ql/src/Security/CWE/CWE-079/XSS.java | 8 + java/ql/src/Security/CWE/CWE-079/XSS.qhelp | 41 + java/ql/src/Security/CWE/CWE-079/XSS.ql | 26 + .../src/Security/CWE/CWE-079/XSSLocal.qhelp | 5 + java/ql/src/Security/CWE/CWE-079/XSSLocal.ql | 25 + .../Security/CWE/CWE-089/HowToAddress.qhelp | 32 + .../Security/CWE/CWE-089/SqlInjectionLib.qll | 49 + .../src/Security/CWE/CWE-089/SqlTainted.java | 17 + .../src/Security/CWE/CWE-089/SqlTainted.qhelp | 84 + .../ql/src/Security/CWE/CWE-089/SqlTainted.ql | 21 + .../CWE/CWE-089/SqlTaintedLocal.qhelp | 5 + .../Security/CWE/CWE-089/SqlTaintedLocal.ql | 27 + .../CWE/CWE-089/SqlTaintedPersistence.java | 48 + .../Security/CWE/CWE-089/SqlUnescaped.java | 17 + .../Security/CWE/CWE-089/SqlUnescaped.qhelp | 55 + .../src/Security/CWE/CWE-089/SqlUnescaped.ql | 44 + .../CWE/CWE-113/ResponseSplitting.java | 17 + .../CWE/CWE-113/ResponseSplitting.qhelp | 41 + .../Security/CWE/CWE-113/ResponseSplitting.ql | 27 + .../CWE/CWE-113/ResponseSplitting.qll | 34 + .../CWE/CWE-113/ResponseSplittingLocal.qhelp | 5 + .../CWE/CWE-113/ResponseSplittingLocal.ql | 25 + .../src/Security/CWE/CWE-129/ArraySizing.qll | 195 + .../Security/CWE/CWE-129/BoundingChecks.qll | 62 + ...ImproperValidationOfArrayConstruction.java | 28 + ...mproperValidationOfArrayConstruction.qhelp | 38 + .../ImproperValidationOfArrayConstruction.ql | 31 + ...ationOfArrayConstructionCodeSpecified.java | 23 + ...tionOfArrayConstructionCodeSpecified.qhelp | 36 + ...idationOfArrayConstructionCodeSpecified.ql | 35 + ...erValidationOfArrayConstructionLocal.qhelp | 5 + ...roperValidationOfArrayConstructionLocal.ql | 32 + .../ImproperValidationOfArrayIndex.java | 29 + .../ImproperValidationOfArrayIndex.qhelp | 36 + .../CWE-129/ImproperValidationOfArrayIndex.ql | 31 + ...erValidationOfArrayIndexCodeSpecified.java | 26 + ...rValidationOfArrayIndexCodeSpecified.qhelp | 48 + ...operValidationOfArrayIndexCodeSpecified.ql | 58 + .../ImproperValidationOfArrayIndexLocal.qhelp | 5 + .../ImproperValidationOfArrayIndexLocal.ql | 31 + .../ExternallyControlledFormatString.java | 27 + .../ExternallyControlledFormatString.qhelp | 49 + .../ExternallyControlledFormatString.ql | 28 + ...xternallyControlledFormatStringLocal.qhelp | 5 + .../ExternallyControlledFormatStringLocal.ql | 27 + .../Security/CWE/CWE-190/ArithmeticCommon.qll | 115 + .../CWE/CWE-190/ArithmeticTainted.java | 29 + .../CWE/CWE-190/ArithmeticTainted.qhelp | 47 + .../Security/CWE/CWE-190/ArithmeticTainted.ql | 41 + .../CWE/CWE-190/ArithmeticTaintedLocal.qhelp | 5 + .../CWE/CWE-190/ArithmeticTaintedLocal.ql | 41 + .../CWE/CWE-190/ArithmeticUncontrolled.java | 19 + .../CWE/CWE-190/ArithmeticUncontrolled.qhelp | 47 + .../CWE/CWE-190/ArithmeticUncontrolled.ql | 69 + .../CWE-190/ArithmeticWithExtremeValues.java | 15 + .../CWE-190/ArithmeticWithExtremeValues.qhelp | 36 + .../CWE-190/ArithmeticWithExtremeValues.ql | 87 + .../CWE/CWE-190/ComparisonWithWiderType.java | 36 + .../CWE/CWE-190/ComparisonWithWiderType.qhelp | 42 + .../CWE/CWE-190/ComparisonWithWiderType.ql | 70 + .../CWE/CWE-209/StackTraceExposure.java | 20 + .../CWE/CWE-209/StackTraceExposure.qhelp | 47 + .../CWE/CWE-209/StackTraceExposure.ql | 125 + .../CWE/CWE-312/CleartextStorage.java | 28 + .../CWE/CWE-312/CleartextStorage.qhelp | 43 + .../CWE/CWE-312/CleartextStorageClass.qhelp | 5 + .../CWE/CWE-312/CleartextStorageClass.ql | 26 + .../CWE/CWE-312/CleartextStorageCookie.qhelp | 5 + .../CWE/CWE-312/CleartextStorageCookie.ql | 25 + .../CWE-312/CleartextStorageProperties.qhelp | 5 + .../CWE/CWE-312/CleartextStorageProperties.ql | 25 + .../Security/CWE/CWE-312/SensitiveStorage.qll | 240 + .../src/Security/CWE/CWE-319/HttpsUrls.java | 35 + .../src/Security/CWE/CWE-319/HttpsUrls.qhelp | 49 + java/ql/src/Security/CWE/CWE-319/HttpsUrls.ql | 70 + java/ql/src/Security/CWE/CWE-319/UseSSL.java | 31 + java/ql/src/Security/CWE/CWE-319/UseSSL.qhelp | 50 + java/ql/src/Security/CWE/CWE-319/UseSSL.ql | 39 + .../CWE/CWE-319/UseSSLSocketFactories.java | 25 + .../CWE/CWE-319/UseSSLSocketFactories.qhelp | 44 + .../CWE/CWE-319/UseSSLSocketFactories.ql | 81 + .../CWE/CWE-327/BrokenCryptoAlgorithm.java | 12 + .../CWE/CWE-327/BrokenCryptoAlgorithm.qhelp | 41 + .../CWE/CWE-327/BrokenCryptoAlgorithm.ql | 46 + .../CWE-327/MaybeBrokenCryptoAlgorithm.qhelp | 5 + .../CWE/CWE-327/MaybeBrokenCryptoAlgorithm.ql | 73 + .../Security/CWE/CWE-335/PredictableSeed.java | 14 + .../CWE/CWE-335/PredictableSeed.qhelp | 36 + .../Security/CWE/CWE-335/PredictableSeed.ql | 18 + .../src/Security/CWE/CWE-367/TOCTOURace.java | 27 + .../src/Security/CWE/CWE-367/TOCTOURace.qhelp | 53 + .../ql/src/Security/CWE/CWE-367/TOCTOURace.ql | 132 + .../Security/CWE/CWE-421/SocketAuthRace.java | 19 + .../Security/CWE/CWE-421/SocketAuthRace.qhelp | 43 + .../Security/CWE/CWE-421/SocketAuthRace.ql | 83 + .../CWE/CWE-502/UnsafeDeserialization.qhelp | 80 + .../CWE/CWE-502/UnsafeDeserialization.ql | 24 + .../CWE/CWE-502/UnsafeDeserialization.qll | 71 + .../CWE/CWE-502/UnsafeDeserializationBad.java | 12 + .../CWE-502/UnsafeDeserializationGood.java | 5 + .../src/Security/CWE/CWE-601/UrlRedirect.java | 14 + .../Security/CWE/CWE-601/UrlRedirect.qhelp | 36 + .../src/Security/CWE/CWE-601/UrlRedirect.ql | 25 + .../src/Security/CWE/CWE-601/UrlRedirect.qll | 23 + .../CWE/CWE-601/UrlRedirectLocal.qhelp | 5 + .../Security/CWE/CWE-601/UrlRedirectLocal.ql | 25 + java/ql/src/Security/CWE/CWE-611/XXE.qhelp | 70 + java/ql/src/Security/CWE/CWE-611/XXE.ql | 42 + java/ql/src/Security/CWE/CWE-611/XXEBad.java | 5 + java/ql/src/Security/CWE/CWE-611/XXEGood.java | 6 + .../src/Security/CWE/CWE-611/XmlParsers.qll | 1108 + .../Security/CWE/CWE-614/InsecureCookie.java | 16 + .../Security/CWE/CWE-614/InsecureCookie.qhelp | 37 + .../Security/CWE/CWE-614/InsecureCookie.ql | 24 + .../CWE-676/PotentiallyDangerousFunction.java | 16 + .../PotentiallyDangerousFunction.qhelp | 57 + .../CWE-676/PotentiallyDangerousFunction.ql | 23 + .../CWE/CWE-681/NumericCastCommon.qll | 68 + .../CWE/CWE-681/NumericCastTainted.java | 29 + .../CWE/CWE-681/NumericCastTainted.qhelp | 50 + .../CWE/CWE-681/NumericCastTainted.ql | 36 + .../CWE/CWE-681/NumericCastTaintedLocal.qhelp | 5 + .../CWE/CWE-681/NumericCastTaintedLocal.ql | 36 + .../ReadingFromWorldWritableFile.qhelp | 53 + .../CWE-732/ReadingFromWorldWritableFile.ql | 25 + .../CWE/CWE-798/HardcodedCredentials.qll | 104 + .../CWE-798/HardcodedCredentialsApiCall.java | 12 + .../CWE-798/HardcodedCredentialsApiCall.qhelp | 44 + .../CWE-798/HardcodedCredentialsApiCall.ql | 36 + .../HardcodedCredentialsComparison.qhelp | 6 + .../CWE-798/HardcodedCredentialsComparison.ql | 29 + .../HardcodedCredentialsSourceCall.qhelp | 6 + .../CWE-798/HardcodedCredentialsSourceCall.ql | 47 + .../CWE/CWE-798/HardcodedPasswordField.qhelp | 6 + .../CWE/CWE-798/HardcodedPasswordField.ql | 20 + .../src/Security/CWE/CWE-798/SensitiveApi.qll | 424 + .../CWE/CWE-807/ConditionalBypass.java | 22 + .../CWE/CWE-807/ConditionalBypass.qhelp | 47 + .../Security/CWE/CWE-807/ConditionalBypass.ql | 43 + .../CWE/CWE-807/TaintedPermissionsCheck.java | 12 + .../CWE/CWE-807/TaintedPermissionsCheck.qhelp | 49 + .../CWE/CWE-807/TaintedPermissionsCheck.ql | 66 + .../CWE/CWE-833/LockOrderInconsistency.java | 33 + .../CWE/CWE-833/LockOrderInconsistency.qhelp | 61 + .../CWE/CWE-833/LockOrderInconsistency.ql | 173 + .../Security/CWE/CWE-835/InfiniteLoop.qhelp | 49 + .../src/Security/CWE/CWE-835/InfiniteLoop.ql | 57 + .../Security/CWE/CWE-835/InfiniteLoopBad.java | 6 + .../CWE/CWE-835/InfiniteLoopGood.java | 6 + .../Boolean Logic/SimplifyBoolExpr.java | 30 + .../Boolean Logic/SimplifyBoolExpr.qhelp | 91 + .../Boolean Logic/SimplifyBoolExpr.ql | 75 + .../Boxed Types/BoxedVariable.qhelp | 67 + .../Boxed Types/BoxedVariable.ql | 71 + .../Boxed Types/BoxedVariableBad.java | 6 + .../Boxed Types/BoxedVariableGood.java | 6 + .../Comments/CommentedCode.qhelp | 7 + .../Comments/CommentedCode.ql | 17 + .../Comments/CommentedCode.qll | 136 + .../Comments/TodoComments.qhelp | 53 + .../Comments/TodoComments.ql | 19 + .../Dead Code/AssignmentInReturn.java | 7 + .../Dead Code/AssignmentInReturn.qhelp | 44 + .../Dead Code/AssignmentInReturn.ql | 17 + .../Dead Code/CreatesEmptyZip.java | 21 + .../Dead Code/CreatesEmptyZip.qhelp | 45 + .../Dead Code/CreatesEmptyZip.ql | 55 + .../Dead Code/DeadLocals.qll | 123 + .../Dead Code/DeadRefTypes.qhelp | 29 + .../Dead Code/DeadRefTypes.ql | 56 + .../Dead Code/DeadStoreOfLocal.java | 7 + .../Dead Code/DeadStoreOfLocal.qhelp | 35 + .../Dead Code/DeadStoreOfLocal.ql | 51 + .../Dead Code/DeadStoreOfLocalUnread.qhelp | 20 + .../Dead Code/DeadStoreOfLocalUnread.ql | 27 + .../Dead Code/EmptyFinalize.java | 21 + .../Dead Code/EmptyFinalize.qhelp | 38 + .../Dead Code/EmptyFinalize.ql | 21 + .../Dead Code/FinalizerNullsFields.java | 14 + .../Dead Code/FinalizerNullsFields.qhelp | 95 + .../Dead Code/FinalizerNullsFields.ql | 21 + .../InterfaceCannotBeImplemented.java | 9 + .../InterfaceCannotBeImplemented.qhelp | 59 + .../Dead Code/InterfaceCannotBeImplemented.ql | 29 + .../LocalInitialisedButNotUsed.qhelp | 21 + .../Dead Code/LocalInitialisedButNotUsed.ql | 21 + .../Dead Code/LocalNotRead.qhelp | 21 + .../Dead Code/LocalNotRead.ql | 17 + .../Dead Code/NonAssignedFields.java | 16 + .../Dead Code/NonAssignedFields.qhelp | 42 + .../Dead Code/NonAssignedFields.ql | 90 + .../Dead Code/PointlessForwardingMethod.java | 41 + .../Dead Code/PointlessForwardingMethod.qhelp | 31 + .../Dead Code/PointlessForwardingMethod.ql | 39 + .../Dead Code/UnreadLocal.java | 25 + .../Dead Code/UnreadLocal.qhelp | 42 + .../Dead Code/UnreadLocal.ql | 30 + .../Dead Code/UnusedField.qhelp | 35 + .../Dead Code/UnusedField.ql | 30 + .../Dead Code/UnusedLabel.java | 16 + .../Dead Code/UnusedLabel.qhelp | 49 + .../Dead Code/UnusedLabel.ql | 18 + .../Dead Code/UnusedLocal.qhelp | 30 + .../Dead Code/UnusedLocal.ql | 31 + .../Declarations/BreakInSwitchCase.java | 25 + .../Declarations/BreakInSwitchCase.qhelp | 54 + .../Declarations/BreakInSwitchCase.ql | 23 + .../Declarations/Common.qll | 68 + .../Declarations/MakeImportsExplicit.java | 8 + .../Declarations/MakeImportsExplicit.qhelp | 63 + .../Declarations/MakeImportsExplicit.ql | 14 + .../Declarations/NoConstantsOnly.java | 15 + .../Declarations/NoConstantsOnly.qhelp | 65 + .../Declarations/NoConstantsOnly.ql | 54 + .../DroppedExceptions-comment.java | 9 + .../DroppedExceptions-good.java | 10 + .../DroppedExceptions-ignore.java | 5 + .../Exception Handling/DroppedExceptions.java | 10 + .../DroppedExceptions.qhelp | 62 + .../Exception Handling/DroppedExceptions.ql | 21 + .../Exception Handling/ExceptionCatch.java | 13 + .../Exception Handling/ExceptionCatch.qhelp | 51 + .../Exception Handling/ExceptionCatch.ql | 59 + .../IgnoreExceptionalReturn.java | 3 + .../IgnoreExceptionalReturn.qhelp | 88 + .../IgnoreExceptionalReturn.ql | 60 + .../AbstractToConcreteCollection.java | 12 + .../AbstractToConcreteCollection.qhelp | 59 + .../AbstractToConcreteCollection.ql | 60 + .../ExposeRepresentation.java | 18 + .../ExposeRepresentation.qhelp | 67 + .../ExposeRepresentation.ql | 118 + .../GetClassGetResource.java | 17 + .../GetClassGetResource.qhelp | 59 + .../GetClassGetResource.ql | 34 + .../GetClassGetResourceGood.java | 17 + .../Implementation Hiding/StaticArray.java | 11 + .../Implementation Hiding/StaticArray.qhelp | 62 + .../Implementation Hiding/StaticArray.ql | 45 + .../StaticArrayGood.java | 42 + .../Magic Constants/MagicConstants.qll | 347 + .../MagicConstantsNumbers.java | 36 + .../MagicConstantsNumbers.qhelp | 53 + .../Magic Constants/MagicConstantsNumbers.ql | 20 + .../Magic Constants/MagicConstantsString.java | 36 + .../MagicConstantsString.qhelp | 53 + .../Magic Constants/MagicConstantsString.ql | 78 + .../MagicNumbersUseConstant.java | 37 + .../MagicNumbersUseConstant.qhelp | 53 + .../MagicNumbersUseConstant.ql | 19 + .../MagicStringsUseConstant.java | 37 + .../MagicStringsUseConstant.qhelp | 53 + .../MagicStringsUseConstant.ql | 17 + .../AmbiguousOuterSuper.java | 24 + .../AmbiguousOuterSuper.qhelp | 48 + .../Naming Conventions/AmbiguousOuterSuper.ql | 59 + .../ConfusingMethodNames.java | 18 + .../ConfusingMethodNames.qhelp | 35 + .../ConfusingMethodNames.ql | 29 + .../ConfusingOverloading.qhelp | 51 + .../ConfusingOverloading.ql | 123 + .../ConfusingOverridesNames.java | 12 + .../ConfusingOverridesNames.qhelp | 45 + .../ConfusingOverridesNames.ql | 75 + .../FieldMasksSuperField.java | 24 + .../FieldMasksSuperField.qhelp | 41 + .../FieldMasksSuperField.ql | 35 + .../LocalShadowsField.qhelp | 23 + .../Naming Conventions/LocalShadowsField.ql | 23 + .../LocalShadowsFieldConfusing.java | 15 + .../LocalShadowsFieldConfusing.qhelp | 41 + .../LocalShadowsFieldConfusing.ql | 32 + .../Naming Conventions/SameNameAsSuper.java | 16 + .../Naming Conventions/SameNameAsSuper.qhelp | 38 + .../Naming Conventions/SameNameAsSuper.ql | 21 + .../Naming Conventions/Shadowing.qll | 83 + .../CallsToRunFinalizersOnExit.java | 8 + .../CallsToRunFinalizersOnExit.qhelp | 72 + .../CallsToRunFinalizersOnExit.ql | 25 + .../CallsToRunFinalizersOnExitGood.java | 23 + .../CallsToStringToString.java | 9 + .../CallsToStringToString.qhelp | 42 + .../CallsToStringToString.ql | 17 + .../Undesirable Calls/CallsToSystemExit.java | 24 + .../Undesirable Calls/CallsToSystemExit.qhelp | 66 + .../Undesirable Calls/CallsToSystemExit.ql | 27 + .../Undesirable Calls/DefaultToString.java | 21 + .../Undesirable Calls/DefaultToString.qhelp | 54 + .../Undesirable Calls/DefaultToString.ql | 62 + .../DefaultToStringGood.java | 26 + .../Undesirable Calls/GarbageCollection.java | 15 + .../Undesirable Calls/GarbageCollection.qhelp | 50 + .../Undesirable Calls/GarbageCollection.ql | 22 + .../Undesirable Calls/NextFromIterator.java | 26 + .../Undesirable Calls/NextFromIterator.qhelp | 49 + .../Undesirable Calls/NextFromIterator.ql | 28 + .../Undesirable Calls/PrintLnArray.java | 24 + .../Undesirable Calls/PrintLnArray.qhelp | 49 + .../Undesirable Calls/PrintLnArray.ql | 32 + .../legacy/AutoBoxing.java | 4 + .../legacy/AutoBoxing.qhelp | 72 + .../legacy/AutoBoxing.ql | 93 + .../legacy/FinallyMayNotComplete.qhelp | 50 + .../legacy/FinallyMayNotComplete.ql | 37 + .../legacy/InexactVarArg.java | 13 + .../legacy/InexactVarArg.qhelp | 81 + .../legacy/InexactVarArg.ql | 42 + .../legacy/ParameterAssignment.java | 18 + .../legacy/ParameterAssignment.qhelp | 49 + .../legacy/ParameterAssignment.ql | 16 + .../legacy/UnnecessaryCast.java | 6 + .../legacy/UnnecessaryCast.qhelp | 38 + .../legacy/UnnecessaryCast.ql | 18 + .../legacy/UnnecessaryImport.qhelp | 35 + .../legacy/UnnecessaryImport.ql | 77 + java/ql/src/config/semmlecode.dbscheme | 821 + java/ql/src/config/semmlecode.dbscheme.stats | 23441 ++++++++++++++++ java/ql/src/default.qll | 1 + java/ql/src/definitions.ql | 185 + java/ql/src/external/Clover.qll | 137 + java/ql/src/external/CodeDuplication.qll | 290 + java/ql/src/external/DefectFilter.qll | 51 + java/ql/src/external/DuplicateAnonymous.java | 30 + java/ql/src/external/DuplicateAnonymous.qhelp | 43 + java/ql/src/external/DuplicateAnonymous.ql | 23 + java/ql/src/external/DuplicateBlock.ql | 21 + java/ql/src/external/DuplicateMethod.java | 23 + java/ql/src/external/DuplicateMethod.qhelp | 62 + java/ql/src/external/DuplicateMethod.ql | 31 + java/ql/src/external/ExternalArtifact.qll | 44 + java/ql/src/external/MetricFilter.qll | 37 + .../src/external/MostlyDuplicateClass.qhelp | 43 + java/ql/src/external/MostlyDuplicateClass.ql | 23 + .../ql/src/external/MostlyDuplicateFile.qhelp | 44 + java/ql/src/external/MostlyDuplicateFile.ql | 22 + .../src/external/MostlyDuplicateMethod.qhelp | 48 + java/ql/src/external/MostlyDuplicateMethod.ql | 30 + java/ql/src/external/MostlySimilarFile.qhelp | 39 + java/ql/src/external/MostlySimilarFile.ql | 22 + java/ql/src/external/VCS.qll | 90 + java/ql/src/filters/ClassifyFiles.ql | 20 + java/ql/src/filters/FromSource.ql | 14 + .../src/filters/ImportAdditionalLibraries.ql | 17 + java/ql/src/filters/NotGenerated.ql | 12 + java/ql/src/filters/NotGeneratedForMetric.ql | 12 + java/ql/src/filters/RecentDefects.ql | 21 + java/ql/src/filters/RecentDefectsForMetric.ql | 21 + java/ql/src/filters/SuppressionComment.ql | 52 + java/ql/src/java.qll | 40 + java/ql/src/meta/ssa/AmbiguousToString.ql | 31 + java/ql/src/meta/ssa/TooFewPhiInputs.ql | 18 + .../src/meta/ssa/UncertainDefWithoutPrior.ql | 25 + .../meta/ssa/UseWithoutUniqueSsaVariable.ql | 47 + java/ql/src/plugin.xml | 16 + java/ql/src/queries.xml | 1 + java/ql/src/semmle/code/FileSystem.qll | 279 + java/ql/src/semmle/code/Location.qll | 158 + java/ql/src/semmle/code/java/Annotation.qll | 158 + java/ql/src/semmle/code/java/Collections.qll | 130 + .../src/semmle/code/java/CompilationUnit.qll | 41 + java/ql/src/semmle/code/java/Completion.qll | 87 + java/ql/src/semmle/code/java/Concurrency.qll | 48 + .../src/semmle/code/java/ControlFlowGraph.qll | 1009 + java/ql/src/semmle/code/java/Conversions.qll | 172 + java/ql/src/semmle/code/java/Dependency.qll | 117 + .../src/semmle/code/java/DependencyCounts.qll | 150 + java/ql/src/semmle/code/java/Element.qll | 61 + java/ql/src/semmle/code/java/Exception.qll | 30 + java/ql/src/semmle/code/java/Expr.qll | 1663 ++ .../src/semmle/code/java/GeneratedFiles.qll | 70 + java/ql/src/semmle/code/java/Generics.qll | 565 + java/ql/src/semmle/code/java/Import.qll | 140 + java/ql/src/semmle/code/java/J2EE.qll | 77 + java/ql/src/semmle/code/java/JDK.qll | 428 + .../src/semmle/code/java/JDKAnnotations.qll | 136 + java/ql/src/semmle/code/java/JMX.qll | 103 + java/ql/src/semmle/code/java/Javadoc.qll | 170 + java/ql/src/semmle/code/java/Maps.qll | 102 + java/ql/src/semmle/code/java/Member.qll | 645 + java/ql/src/semmle/code/java/Modifier.qll | 69 + java/ql/src/semmle/code/java/Modules.qll | 211 + java/ql/src/semmle/code/java/Package.qll | 34 + java/ql/src/semmle/code/java/Reflection.qll | 400 + .../src/semmle/code/java/Serializability.qll | 33 + java/ql/src/semmle/code/java/Statement.qll | 825 + java/ql/src/semmle/code/java/StringFormat.qll | 422 + java/ql/src/semmle/code/java/Type.qll | 1045 + java/ql/src/semmle/code/java/UnitTests.qll | 325 + java/ql/src/semmle/code/java/Variable.qll | 106 + .../semmle/code/java/arithmetic/Overflow.qll | 124 + .../code/java/comparison/Comparison.qll | 23 + .../code/java/controlflow/BasicBlocks.qll | 69 + .../code/java/controlflow/Dominance.qll | 139 + .../semmle/code/java/controlflow/Guards.qll | 233 + .../semmle/code/java/controlflow/Paths.qll | 85 + .../java/controlflow/UnreachableBlocks.qll | 241 + .../java/controlflow/internal/GuardsLogic.qll | 306 + .../ExcludeDebuggingProfilingLogging.qll | 39 + .../semmle/code/java/dataflow/DataFlow.qll | 33 + .../semmle/code/java/dataflow/DataFlow2.qll | 33 + .../semmle/code/java/dataflow/DataFlow3.qll | 33 + .../semmle/code/java/dataflow/DataFlow4.qll | 33 + .../semmle/code/java/dataflow/DataFlow5.qll | 33 + .../src/semmle/code/java/dataflow/DefUse.qll | 54 + .../semmle/code/java/dataflow/FlowSources.qll | 225 + .../src/semmle/code/java/dataflow/Guards.qll | 65 + .../code/java/dataflow/InstanceAccess.qll | 259 + .../code/java/dataflow/IntegerGuards.qll | 101 + .../semmle/code/java/dataflow/NullGuards.qll | 217 + .../semmle/code/java/dataflow/Nullness.qll | 673 + .../code/java/dataflow/ParityAnalysis.qll | 227 + .../code/java/dataflow/RangeAnalysis.qll | 708 + .../semmle/code/java/dataflow/RangeUtils.qll | 133 + java/ql/src/semmle/code/java/dataflow/SSA.qll | 1104 + .../code/java/dataflow/SignAnalysis.qll | 447 + .../code/java/dataflow/TaintTracking.qll | 689 + .../semmle/code/java/dataflow/TypeFlow.qll | 339 + .../code/java/dataflow/internal/BaseSSA.qll | 416 + .../dataflow/internal/DataFlowDispatch.qll | 178 + .../java/dataflow/internal/DataFlowImpl.qll | 1536 + .../java/dataflow/internal/DataFlowImpl2.qll | 1536 + .../java/dataflow/internal/DataFlowImpl3.qll | 1536 + .../java/dataflow/internal/DataFlowImpl4.qll | 1536 + .../java/dataflow/internal/DataFlowImpl5.qll | 1536 + .../dataflow/internal/DataFlowImplCommon.qll | 274 + .../dataflow/internal/DataFlowImplDepr.qll | 1536 + .../dataflow/internal/DataFlowPrivate.qll | 211 + .../java/dataflow/internal/DataFlowUtil.qll | 354 + .../semmle/code/java/deadcode/DeadCode.qll | 328 + .../java/deadcode/DeadCodeCustomizations.qll | 0 .../code/java/deadcode/DeadEnumConstant.qll | 77 + .../semmle/code/java/deadcode/DeadField.qll | 177 + .../semmle/code/java/deadcode/EntryPoints.qll | 487 + .../code/java/deadcode/SpringEntryPoints.qll | 132 + .../code/java/deadcode/StrutsEntryPoints.qll | 96 + .../code/java/deadcode/TestEntryPoints.qll | 181 + .../code/java/deadcode/WebEntryPoints.qll | 112 + .../deadcode/frameworks/CamelEntryPoints.qll | 20 + .../frameworks/FitNesseEntryPoints.qll | 81 + .../frameworks/GigaSpacesXAPEntryPoints.qll | 42 + .../code/java/dispatch/DispatchFlow.qll | 258 + .../code/java/dispatch/VirtualDispatch.qll | 319 + .../code/java/dispatch/WrappedInvocation.qll | 81 + .../code/java/frameworks/ApacheHttp.qll | 15 + .../code/java/frameworks/Assertions.qll | 103 + .../src/semmle/code/java/frameworks/Camel.qll | 103 + .../semmle/code/java/frameworks/Cucumber.qll | 40 + .../src/semmle/code/java/frameworks/JAXB.qll | 220 + .../code/java/frameworks/JUnitAnnotations.qll | 84 + .../code/java/frameworks/JavaxAnnotations.qll | 126 + .../src/semmle/code/java/frameworks/JaxWS.qll | 231 + .../src/semmle/code/java/frameworks/Jdbc.qll | 111 + .../src/semmle/code/java/frameworks/Kryo.qll | 51 + .../semmle/code/java/frameworks/Lombok.qll | 253 + .../semmle/code/java/frameworks/Mockito.qll | 413 + .../code/java/frameworks/Networking.qll | 33 + .../code/java/frameworks/Properties.qll | 34 + .../src/semmle/code/java/frameworks/Rmi.qll | 22 + .../semmle/code/java/frameworks/Selenium.qll | 44 + .../semmle/code/java/frameworks/Servlets.qll | 352 + .../semmle/code/java/frameworks/SnakeYaml.qll | 95 + .../semmle/code/java/frameworks/XStream.qll | 45 + .../code/java/frameworks/android/Intent.qll | 51 + .../code/java/frameworks/android/SQLite.qll | 49 + .../code/java/frameworks/android/WebView.qll | 40 + .../java/frameworks/android/XmlParsing.qll | 27 + .../code/java/frameworks/apache/Exec.qll | 26 + .../frameworks/camel/CamelJavaAnnotations.qll | 44 + .../java/frameworks/camel/CamelJavaDSL.qll | 127 + .../java/frameworks/gigaspaces/GigaSpaces.qll | 71 + .../frameworks/google/GoogleHttpClientApi.qll | 40 + .../semmle/code/java/frameworks/gwt/GWT.qll | 115 + .../code/java/frameworks/gwt/GwtUiBinder.qll | 105 + .../java/frameworks/gwt/GwtUiBinderXml.qll | 49 + .../code/java/frameworks/gwt/GwtXml.qll | 141 + .../code/java/frameworks/j2objc/J2ObjC.qll | 49 + .../jackson/JacksonSerializability.qll | 236 + .../frameworks/javaee/JavaServerFaces.qll | 77 + .../java/frameworks/javaee/Persistence.qll | 548 + .../java/frameworks/javaee/PersistenceXML.qll | 89 + .../code/java/frameworks/javaee/ejb/EJB.qll | 933 + .../java/frameworks/javaee/ejb/EJBJarXML.qll | 200 + .../frameworks/javaee/ejb/EJBRestrictions.qll | 473 + .../frameworks/javaee/jsf/JSFAnnotations.qll | 35 + .../javaee/jsf/JSFFacesContextXML.qll | 84 + .../code/java/frameworks/spring/Spring.qll | 40 + .../frameworks/spring/SpringAbstractRef.qll | 46 + .../java/frameworks/spring/SpringAlias.qll | 28 + .../java/frameworks/spring/SpringArgType.qll | 14 + .../frameworks/spring/SpringAttribute.qll | 19 + .../java/frameworks/spring/SpringAutowire.qll | 390 + .../java/frameworks/spring/SpringBean.qll | 475 + .../java/frameworks/spring/SpringBeanFile.qll | 97 + .../frameworks/spring/SpringBeanRefType.qll | 17 + .../java/frameworks/spring/SpringCamel.qll | 147 + .../frameworks/spring/SpringComponentScan.qll | 213 + .../spring/SpringConstructorArg.qll | 93 + .../frameworks/spring/SpringController.qll | 150 + .../frameworks/spring/SpringDescription.qll | 13 + .../java/frameworks/spring/SpringEntry.qll | 93 + .../java/frameworks/spring/SpringFlex.qll | 92 + .../java/frameworks/spring/SpringIdRef.qll | 9 + .../java/frameworks/spring/SpringImport.qll | 14 + .../spring/SpringInitializingBean.qll | 21 + .../code/java/frameworks/spring/SpringKey.qll | 9 + .../java/frameworks/spring/SpringList.qll | 9 + .../frameworks/spring/SpringListOrSet.qll | 23 + .../frameworks/spring/SpringLookupMethod.qll | 52 + .../code/java/frameworks/spring/SpringMap.qll | 29 + .../java/frameworks/spring/SpringMergable.qll | 18 + .../java/frameworks/spring/SpringMeta.qll | 19 + .../java/frameworks/spring/SpringNull.qll | 9 + .../java/frameworks/spring/SpringProfile.qll | 108 + .../java/frameworks/spring/SpringProp.qll | 14 + .../java/frameworks/spring/SpringProperty.qll | 91 + .../java/frameworks/spring/SpringProps.qll | 9 + .../frameworks/spring/SpringQualifier.qll | 26 + .../code/java/frameworks/spring/SpringRef.qll | 26 + .../spring/SpringReplacedMethod.qll | 25 + .../code/java/frameworks/spring/SpringSet.qll | 9 + .../java/frameworks/spring/SpringValue.qll | 19 + .../frameworks/spring/SpringXMLElement.qll | 49 + .../spring/metrics/MetricSpringBean.qll | 73 + .../spring/metrics/MetricSpringBeanFile.qll | 21 + .../java/frameworks/struts/StrutsActions.qll | 136 + .../frameworks/struts/StrutsAnnotations.qll | 44 + .../frameworks/struts/StrutsConventions.qll | 127 + .../code/java/frameworks/struts/StrutsXML.qll | 231 + .../code/java/metrics/MetricCallable.qll | 107 + .../code/java/metrics/MetricElement.qll | 155 + .../semmle/code/java/metrics/MetricField.qll | 16 + .../code/java/metrics/MetricPackage.qll | 264 + .../code/java/metrics/MetricRefType.qll | 351 + .../semmle/code/java/metrics/MetricStmt.qll | 28 + .../code/java/security/ControlledString.qll | 93 + .../semmle/code/java/security/DataFlow.qll | 838 + .../semmle/code/java/security/Encryption.qll | 260 + .../code/java/security/ExternalProcess.qll | 39 + .../code/java/security/FileReadWrite.qll | 60 + .../code/java/security/FileWritable.qll | 141 + .../src/semmle/code/java/security/Random.qll | 149 + .../code/java/security/RelativePaths.qll | 75 + .../code/java/security/SecurityTests.qll | 13 + .../code/java/security/SensitiveActions.qll | 96 + .../code/java/security/SqlUnescapedLib.qll | 44 + .../semmle/code/java/security/Validation.qll | 37 + java/ql/src/semmle/code/java/security/XSS.qll | 55 + java/ql/src/semmle/code/xml/Ant.qll | 41 + java/ql/src/semmle/code/xml/MavenPom.qll | 479 + java/ql/src/semmle/code/xml/WebXML.qll | 160 + java/ql/src/semmle/code/xml/XML.qll | 280 + java/ql/src/semmle/files/FileSystem.qll | 3 + .../test/library-tests/Encryption/Test.java | 32 + .../Encryption/blacklist.expected | 5 + .../library-tests/Encryption/blacklist.ql | 6 + .../Encryption/whitelist.expected | 2 + .../library-tests/Encryption/whitelist.ql | 6 + .../library-tests/ExternalProcess/Test.java | 73 + .../ExternalProcess/argumentToExec.expected | 10 + .../ExternalProcess/argumentToExec.ql | 5 + .../library-tests/ExternalProcess/options | 1 + .../GeneratedFiles/FacebookAutoGen.java | 9 + .../FollowingCodeGenerated.java | 5 + .../GeneratedFiles/Generated.expected | 11 + .../library-tests/GeneratedFiles/Generated.ql | 5 + .../GeneratedFiles/GeneratedClass.expected | 1 + .../GeneratedFiles/GeneratedClass.ql | 4 + .../GeneratedFiles/HasBeenGenerated.java | 8 + .../GeneratedFiles/JavaCharStream.java | 5 + .../JavaParserTokenManager.java | 5 + .../library-tests/GeneratedFiles/Mithra.java | 5 + .../GeneratedFiles/QLParser.java | 5 + .../GeneratedFiles/StandardCharsets.java | 32 + .../library-tests/GeneratedFiles/Test.java | 3 + .../library-tests/GeneratedFiles/Test2.java | 3 + .../library-tests/GeneratedFiles/Test3.java | 4 + .../GeneratedFiles/test/ThriftCompiler.java | 12 + java/ql/test/library-tests/JDK/Main.expected | 1 + java/ql/test/library-tests/JDK/Main.ql | 9 + java/ql/test/library-tests/JDK/jdk/A.java | 29 + .../library-tests/RelativePaths/Test.java | 18 + .../RelativePaths/relativePath.expected | 6 + .../RelativePaths/relativePath.ql | 6 + .../annotations/Annotations.expected | 6 + .../library-tests/annotations/Annotations.ql | 17 + .../annotations/GetAnnotationValue.expected | 43 + .../annotations/GetAnnotationValue.ql | 8 + .../GetLibraryAnnotationElement.expected | 16 + .../GetLibraryAnnotationElement.ql | 11 + .../annotations/SuppressWarnings.expected | 5 + .../annotations/SuppressWarnings.ql | 4 + .../annotations/annotations/A.java | 18 + .../annotations/annotations/Ann.java | 10 + .../annotations/annotations/B.java | 4 + .../annotations/annotations/C.java | 5 + .../annotations/annotations/Container.java | 5 + .../annotations/FieldAnnotations.java | 5 + .../annotations/LocalVarAnnotations.java | 7 + .../annotations/annotations/Pair.java | 6 + .../annotations/ParameterAnnotations.java | 7 + .../annotations/SuppressWarningsExample.java | 20 + .../library-tests/arrays/ArrayInits.expected | 3 + .../test/library-tests/arrays/ArrayInits.ql | 4 + .../library-tests/arrays/Dimension.expected | 5 + .../ql/test/library-tests/arrays/Dimension.ql | 8 + .../library-tests/arrays/ElementType.expected | 5 + .../test/library-tests/arrays/ElementType.ql | 8 + .../test/library-tests/arrays/arrays/A.java | 6 + .../test/library-tests/arrays/arrays/B.java | 9 + .../callgraph/HashCodeCallees.expected | 2 + .../callgraph/HashCodeCallees.ql | 5 + .../library-tests/callgraph/callgraph/A.java | 17 + .../collections/MapMethods.expected | 1 + .../library-tests/collections/MapMethods.ql | 6 + .../library-tests/collections/Maps.expected | 1 + .../ql/test/library-tests/collections/Maps.ql | 7 + .../collections/collections/Test.java | 11 + .../commentedcode/CommentedCode.expected | 8 + .../commentedcode/CommentedCode.java | 125 + .../commentedcode/CommentedCode.ql | 5 + .../library-tests/commentedcode/Test.java | 2 + .../test/library-tests/commentedcode/options | 1 + java/ql/test/library-tests/comments/Test.java | 22 + .../library-tests/comments/TestWindows.java | 22 + .../library-tests/comments/toString.expected | 20 + .../test/library-tests/comments/toString.ql | 4 + .../complexity/Complexity.expected | 4 + .../library-tests/complexity/Complexity.java | 63 + .../library-tests/complexity/Complexity.ql | 5 + .../CompileTimeConstantExpr.expected | 19 + .../constants/CompileTimeConstantExpr.ql | 7 + .../constants/constants/Constants.java | 24 + .../constants/constants/Initializers.java | 39 + .../constants/constants/Values.java | 92 + .../constants/getBooleanValue.expected | 13 + .../constants/getBooleanValue.ql | 9 + .../constants/getInitializer.expected | 6 + .../library-tests/constants/getInitializer.ql | 8 + .../constants/getIntValue.expected | 37 + .../library-tests/constants/getIntValue.ql | 9 + .../constructors/ClassInstanceExpr.expected | 1 + .../constructors/ClassInstanceExpr.ql | 8 + .../constructors/ConstructorCalls.expected | 3 + .../constructors/ConstructorCalls.ql | 11 + .../constructors/InitMethods.expected | 4 + .../library-tests/constructors/InitMethods.ql | 4 + .../constructors/constructors/A.java | 24 + .../library-tests/controlflow/basic/Test.java | 77 + .../controlflow/basic/bbStmts.expected | 136 + .../controlflow/basic/bbStmts.ql | 6 + .../basic/bbStrictDominance.expected | 127 + .../controlflow/basic/bbStrictDominance.ql | 6 + .../controlflow/basic/bbSuccessor.expected | 30 + .../controlflow/basic/bbSuccessor.ql | 5 + .../basic/strictDominance.expected | 628 + .../controlflow/basic/strictDominance.ql | 6 + .../basic/strictPostDominance.expected | 232 + .../controlflow/basic/strictPostDominance.ql | 6 + .../controlflow/dominance/Test.java | 93 + .../controlflow/dominance/Test2.java | 34 + .../dominance/dominanceBad.expected | 0 .../controlflow/dominance/dominanceBad.ql | 8 + .../dominance/dominanceWrong.expected | 0 .../controlflow/dominance/dominanceWrong.ql | 20 + .../dominance/dominatedByStart.expected | 0 .../controlflow/dominance/dominatedByStart.ql | 17 + .../controlflow/dominance/dominator.expected | 176 + .../controlflow/dominance/dominator.ql | 11 + .../dominance/dominatorExists.expected | 0 .../controlflow/dominance/dominatorExists.ql | 18 + .../dominance/dominatorUnique.expected | 0 .../controlflow/dominance/dominatorUnique.ql | 12 + .../library-tests/controlflow/paths/A.java | 49 + .../controlflow/paths/paths.expected | 4 + .../library-tests/controlflow/paths/paths.ql | 16 + .../test/library-tests/dataflow/fields/A.java | 135 + .../test/library-tests/dataflow/fields/B.java | 38 + .../dataflow/fields/flow.expected | 17 + .../library-tests/dataflow/fields/flow.ql | 17 + .../library-tests/dataflow/this-flow/A.java | 42 + .../dataflow/this-flow/this-flow.expected | 28 + .../dataflow/this-flow/this-flow.ql | 20 + java/ql/test/library-tests/defUse/Test.java | 43 + .../test/library-tests/defUse/defUse.expected | 13 + java/ql/test/library-tests/defUse/defUse.ql | 6 + .../defUse/parameterUse.expected | 2 + .../test/library-tests/defUse/parameterUse.ql | 6 + .../test/library-tests/defUse/useUse.expected | 7 + java/ql/test/library-tests/defUse/useUse.ql | 6 + .../dependency-counts/Example.java | 7 + .../dependency-counts/NumDepends.expected | 5 + .../dependency-counts/NumDepends.ql | 7 + .../library-tests/dependency/Depends.expected | 21 + .../test/library-tests/dependency/Depends.ql | 8 + .../dependency/UsesType.expected | 13 + .../test/library-tests/dependency/UsesType.ql | 8 + .../dependency/dependency/A.java | 29 + java/ql/test/library-tests/dispatch/Test.java | 120 + .../dispatch/ViableCallable.java | 139 + .../dispatch/ViableCallable2.java | 29 + .../dispatch/ViableCallable3.java | 20 + .../dispatch/ViableCallableA.java | 22 + .../dispatch/ViableCallableB.java | 6 + .../dispatch/viableCallable.expected | 76 + .../library-tests/dispatch/viableCallable.ql | 8 + .../dispatch/virtualDispatch.expected | 19 + .../library-tests/dispatch/virtualDispatch.ql | 7 + .../fields/FieldAnnotations.expected | 2 + .../library-tests/fields/FieldAnnotations.ql | 6 + .../library-tests/fields/FieldDecl.expected | 7 + .../ql/test/library-tests/fields/FieldDecl.ql | 5 + .../fields/FieldDeclLocation.expected | 3 + .../library-tests/fields/FieldDeclLocation.ql | 5 + .../fields/FieldLocation.expected | 7 + .../library-tests/fields/FieldLocation.ql | 5 + .../fields/fields/FieldTest.java | 7 + .../generics/BinaryTypeVars.expected | 3 + .../library-tests/generics/BinaryTypeVars.ql | 15 + ...meterizedClassInstanceExpressions.expected | 2 + .../ParameterizedClassInstanceExpressions.ql | 5 + .../generics/SourceDeclaration.expected | 10 + .../generics/SourceDeclaration.ql | 10 + .../generics/TypeVarsUpperBound.expected | 6 + .../generics/TypeVarsUpperBound.ql | 6 + .../generics/WildcardsLowerBound.expected | 1 + .../generics/WildcardsLowerBound.ql | 5 + .../library-tests/generics/generics/A.java | 22 + java/ql/test/library-tests/guards/Logic.java | 33 + java/ql/test/library-tests/guards/Test.java | 38 + .../test/library-tests/guards/guards.expected | 41 + java/ql/test/library-tests/guards/guards.ql | 7 + .../library-tests/guards/guardslogic.expected | 32 + .../test/library-tests/guards/guardslogic.ql | 8 + java/ql/test/library-tests/gwt/JSNI.expected | 1 + java/ql/test/library-tests/gwt/JSNI.java | 34 + java/ql/test/library-tests/gwt/JSNI.ql | 5 + .../library-tests/j2objc/OCNIComment.expected | 13 + .../test/library-tests/j2objc/OCNIComment.ql | 11 + java/ql/test/library-tests/j2objc/Test.java | 2 + java/ql/test/library-tests/j2objc/options | 1 + .../library-tests/java7/Diamond/Diamond.java | 20 + .../java7/Diamond/Diamonds.expected | 2 + .../library-tests/java7/Diamond/Diamonds.ql | 5 + .../java7/MultiCatch/MultiCatch.expected | 4 + .../java7/MultiCatch/MultiCatch.java | 43 + .../java7/MultiCatch/MultiCatch.ql | 6 + .../MultiCatch/MultiCatchControlFlow.expected | 48 + .../java7/MultiCatch/MultiCatchControlFlow.ql | 5 + .../javadoc/AllComments.expected | 6 + .../test/library-tests/javadoc/AllComments.ql | 5 + .../javadoc/JavadocComments.expected | 4 + .../library-tests/javadoc/JavadocComments.ql | 4 + .../library-tests/javadoc/javadoc/Test.java | 19 + .../literals/literalBoolean.expected | 1 + .../library-tests/literals/literalBoolean.ql | 4 + .../literals/literalChar.expected | 1 + .../library-tests/literals/literalChar.ql | 4 + .../literals/literalDouble.expected | 1 + .../library-tests/literals/literalDouble.ql | 4 + .../literals/literalFloat.expected | 1 + .../library-tests/literals/literalFloat.ql | 4 + .../literals/literalInteger.expected | 8 + .../library-tests/literals/literalInteger.ql | 4 + .../literals/literalLong.expected | 4 + .../library-tests/literals/literalLong.ql | 4 + .../literals/literalString.expected | 6 + .../library-tests/literals/literalString.ql | 5 + .../literals/literals/Literals.java | 27 + .../LocalVarDeclExprChildren.expected | 8 + .../localvars/LocalVarDeclExprChildren.ql | 5 + .../LocalVarDeclExprTypeAccess.expected | 12 + .../localvars/LocalVarDeclExprTypeAccess.ql | 4 + .../localvars/LocalVarDeclExprs.expected | 12 + .../localvars/LocalVarDeclExprs.ql | 8 + .../LocalVarDeclStmtChildren.expected | 11 + .../localvars/LocalVarDeclStmtChildren.ql | 5 + .../localvars/TypeAccesses.expected | 13 + .../library-tests/localvars/TypeAccesses.ql | 5 + .../localvars/localvars/LocalVarTest.java | 24 + .../locations/EnumLocations.expected | 3 + .../library-tests/locations/EnumLocations.ql | 5 + .../locations/ExceptionLocations.expected | 1 + .../locations/ExceptionLocations.ql | 5 + .../locations/LiteralLocations.expected | 6 + .../locations/LiteralLocations.ql | 5 + .../NegativeLiteralLocation.expected | 2 + .../locations/NegativeLiteralLocation.ql | 5 + .../locations/NewLocations.expected | 5 + .../library-tests/locations/NewLocations.ql | 4 + .../locations/TypeLocations.expected | 25 + .../library-tests/locations/TypeLocations.ql | 14 + .../locations/WildcardLocations.expected | 3 + .../locations/WildcardLocations.ql | 4 + .../library-tests/locations/locations/A.java | 13 + .../library-tests/locations/locations/B.java | 6 + .../library-tests/locations/locations/C.java | 9 + .../library-tests/locations/locations/D.java | 6 + .../library-tests/locations/locations/E.java | 5 + .../library-tests/locations/locations/F.java | 3 + .../library-tests/locations/locations/G.java | 6 + .../modifiers/EnumFinality.expected | 3 + .../library-tests/modifiers/EnumFinality.ql | 6 + .../ql/test/library-tests/modifiers/Test.java | 8 + .../overrides/ConstructedOverrides.expected | 25 + .../overrides/ConstructedOverrides.java | 26 + .../overrides/ConstructedOverrides.ql | 5 + .../overrides/ConstructedOverrides2.expected | 6 + .../overrides/ConstructedOverrides2.ql | 6 + ...Method_getAPossibleImplementation.expected | 19 + .../Method_getAPossibleImplementation.ql | 5 + .../overriding/Method_getAnOverride.expected | 7 + .../overriding/Method_getAnOverride.ql | 5 + .../test/library-tests/overriding/Test.java | 30 + java/ql/test/library-tests/qlengine/Tst.java | 4 + .../qlengine/castAtType.expected | 1 + .../test/library-tests/qlengine/castAtType.ql | 8 + .../qlengine/fromAtType.expected | 1 + .../test/library-tests/qlengine/fromAtType.ql | 4 + .../qlengine/instanceOfAtType.expected | 1 + .../qlengine/instanceOfAtType.ql | 8 + .../qlengine/selectAtType.expected | 1 + .../library-tests/qlengine/selectAtType.ql | 7 + .../reflection/InferClassParameter.expected | 7 + .../reflection/InferClassParameter.ql | 6 + .../reflection/ReflectiveAccess.java | 24 + .../test/library-tests/ssa-large/Large.java | 627 + .../library-tests/ssa-large/countssa.expected | 1 + .../test/library-tests/ssa-large/countssa.ql | 8 + java/ql/test/library-tests/ssa/Fields.java | 50 + java/ql/test/library-tests/ssa/Nested.java | 43 + java/ql/test/library-tests/ssa/Test.java | 33 + .../library-tests/ssa/adjacentUses.expected | 32 + .../ql/test/library-tests/ssa/adjacentUses.ql | 6 + .../test/library-tests/ssa/captures.expected | 9 + java/ql/test/library-tests/ssa/captures.ql | 6 + .../test/library-tests/ssa/firstUse.expected | 60 + java/ql/test/library-tests/ssa/firstUse.ql | 6 + .../ql/test/library-tests/ssa/ssaDef.expected | 93 + java/ql/test/library-tests/ssa/ssaDef.ql | 7 + .../ql/test/library-tests/ssa/ssaPhi.expected | 22 + java/ql/test/library-tests/ssa/ssaPhi.ql | 6 + .../ql/test/library-tests/ssa/ssaUse.expected | 60 + java/ql/test/library-tests/ssa/ssaUse.ql | 6 + .../library-tests/stmts/JumpTargets.expected | 9 + .../test/library-tests/stmts/JumpTargets.ql | 4 + .../library-tests/stmts/SwitchCases.expected | 6 + .../test/library-tests/stmts/SwitchCases.ql | 4 + java/ql/test/library-tests/stmts/stmts/A.java | 14 + java/ql/test/library-tests/stmts/stmts/B.java | 31 + .../structure/DeclaresMember.expected | 46 + .../library-tests/structure/DeclaresMember.ql | 9 + .../structure/EnclosingCallables.expected | 11 + .../structure/EnclosingCallables.ql | 13 + .../structure/EnclosingStatements.expected | 11 + .../structure/EnclosingStatements.ql | 13 + .../structure/HasMethod.expected | 36 + .../test/library-tests/structure/HasMethod.ql | 8 + .../structure/HasObjectMethod.expected | 106 + .../structure/HasObjectMethod.ql | 9 + .../structure/InSameTopLevelType.expected | 57 + .../structure/InSameTopLevelType.ql | 11 + .../library-tests/structure/IsInType.expected | 31 + .../test/library-tests/structure/IsInType.ql | 8 + .../structure/IsNestedType.expected | 4 + .../library-tests/structure/IsNestedType.ql | 8 + .../structure/IsTopLevelType.expected | 5 + .../library-tests/structure/IsTopLevelType.ql | 8 + .../structure/OuterType.expected | 5 + .../test/library-tests/structure/OuterType.ql | 8 + .../structure/TypeGetCompilationUnit.expected | 9 + .../structure/TypeGetCompilationUnit.ql | 12 + .../structure/TypeIsInPackage.expected | 9 + .../structure/TypeIsInPackage.ql | 8 + .../library-tests/structure/structure/A.java | 26 + .../structure/structure/Inherit.java | 52 + .../CloseReaderTest/CloseReaderTest.java | 25 + .../CloseReaderTest/FalseSuccessors.expected | 0 .../CloseReaderTest/FalseSuccessors.ql | 4 + .../PopulateRuntimeException.java | 2 + .../CloseReaderTest/TestSucc.expected | 30 + .../successors/CloseReaderTest/TestSucc.ql | 6 + .../LoopVarReadTest/FalseSuccessors.expected | 1 + .../LoopVarReadTest/FalseSuccessors.ql | 4 + .../LoopVarReadTest/LoopVarReadTest.java | 16 + .../PopulateRuntimeException.java | 2 + .../LoopVarReadTest/TestSucc.expected | 28 + .../successors/LoopVarReadTest/TestSucc.ql | 6 + .../SaveFileTest/FalseSuccessors.expected | 2 + .../SaveFileTest/FalseSuccessors.ql | 4 + .../PopulateRuntimeException.java | 2 + .../successors/SaveFileTest/SaveFileTest.java | 56 + .../successors/SaveFileTest/TestSucc.expected | 103 + .../successors/SaveFileTest/TestSucc.ql | 6 + .../SchackTest/FalseSuccessors.expected | 6 + .../successors/SchackTest/FalseSuccessors.ql | 4 + .../SchackTest/PopulateRuntimeException.java | 2 + .../successors/SchackTest/SchackTest.java | 31 + .../successors/SchackTest/TestSucc.expected | 73 + .../successors/SchackTest/TestSucc.ql | 6 + .../TestBreak/FalseSuccessors.expected | 5 + .../successors/TestBreak/FalseSuccessors.ql | 4 + .../TestBreak/PopulateRuntimeException.java | 2 + .../successors/TestBreak/TestBreak.java | 86 + .../successors/TestBreak/TestSucc.expected | 155 + .../successors/TestBreak/TestSucc.ql | 6 + .../TestContinue/FalseSuccessors.expected | 10 + .../TestContinue/FalseSuccessors.ql | 4 + .../PopulateRuntimeException.java | 2 + .../successors/TestContinue/TestContinue.java | 59 + .../successors/TestContinue/TestSucc.expected | 110 + .../successors/TestContinue/TestSucc.ql | 6 + .../TestDeclarations/FalseSuccessors.expected | 3 + .../TestDeclarations/FalseSuccessors.ql | 4 + .../PopulateRuntimeException.java | 2 + .../TestDeclarations/TestDeclarations.java | 25 + .../TestDeclarations/TestSucc.expected | 54 + .../successors/TestDeclarations/TestSucc.ql | 6 + .../TestFinally/FalseSuccessors.expected | 15 + .../successors/TestFinally/FalseSuccessors.ql | 4 + .../TestFinally/PopulateRuntimeException.java | 2 + .../successors/TestFinally/TestFinally.java | 150 + .../successors/TestFinally/TestSucc.expected | 337 + .../successors/TestFinally/TestSucc.ql | 6 + .../FalseSuccessors.expected | 6 + .../FalseSuccessors.ql | 4 + .../PopulateRuntimeException.java | 2 + .../TestFinallyBreakContinue.java | 108 + .../TestSucc.expected | 141 + .../TestFinallyBreakContinue/TestSucc.ql | 6 + .../TestLoopBranch/FalseSuccessors.expected | 5 + .../TestLoopBranch/FalseSuccessors.ql | 4 + .../PopulateRuntimeException.java | 2 + .../TestLoopBranch/TestLoopBranch.java | 120 + .../TestLoopBranch/TestSucc.expected | 247 + .../successors/TestLoopBranch/TestSucc.ql | 6 + .../TestThrow/FalseSuccessors.expected | 12 + .../successors/TestThrow/FalseSuccessors.ql | 4 + .../TestThrow/PopulateRuntimeException.java | 2 + .../successors/TestThrow/TestSucc.expected | 229 + .../successors/TestThrow/TestSucc.ql | 6 + .../successors/TestThrow/TestThrow.java | 135 + .../TestThrow2/FalseSuccessors.expected | 0 .../successors/TestThrow2/FalseSuccessors.ql | 4 + .../TestThrow2/PopulateRuntimeException.java | 2 + .../successors/TestThrow2/TestSucc.expected | 13 + .../successors/TestThrow2/TestSucc.ql | 6 + .../successors/TestThrow2/TestThrow2.java | 12 + .../TestTryCatch/FalseSuccessors.expected | 1 + .../TestTryCatch/FalseSuccessors.ql | 4 + .../PopulateRuntimeException.java | 2 + .../successors/TestTryCatch/TestSucc.expected | 121 + .../successors/TestTryCatch/TestSucc.ql | 6 + .../successors/TestTryCatch/TestTryCatch.java | 44 + .../FalseSuccessors.expected | 0 .../TestTryWithResources/FalseSuccessors.ql | 4 + .../PopulateRuntimeException.java | 2 + .../TestTryWithResources/TestSucc.expected | 37 + .../TestTryWithResources/TestSucc.ql | 6 + .../TestTryWithResources.java | 17 + .../typeaccesses/ArrayTypeAccesses.expected | 2 + .../typeaccesses/ArrayTypeAccesses.ql | 4 + .../typeaccesses/TypeAccesses.expected | 36 + .../typeaccesses/TypeAccesses.ql | 4 + .../typeaccesses/typeaccesses/Arrays.java | 6 + .../typeaccesses/typeaccesses/Outer.java | 9 + .../typeaccesses/typeaccesses/TA.java | 17 + java/ql/test/library-tests/typeflow/A.java | 73 + .../library-tests/typeflow/typeflow.expected | 16 + .../test/library-tests/typeflow/typeflow.ql | 7 + java/ql/test/library-tests/types/A.java | 10 + .../types/FloatingPointTypes.expected | 4 + .../library-tests/types/FloatingPointTypes.ql | 4 + .../types/IntegralTypes.expected | 10 + .../test/library-tests/types/IntegralTypes.ql | 4 + .../library-tests/types/NumericTypes.expected | 12 + .../test/library-tests/types/NumericTypes.ql | 4 + .../UnreachableBlocks.expected | 9 + .../unreachableblocks/UnreachableBlocks.ql | 5 + .../unreachableblocks/Unreachable.java | 35 + .../library-tests/varargs/Varargs.expected | 4 + java/ql/test/library-tests/varargs/Varargs.ql | 7 + .../library-tests/varargs/varargs/Test.java | 14 + .../AlertSuppression.expected | 48 + .../AlertSuppression/AlertSuppression.qlref | 1 + .../query-tests/AlertSuppression/Test.java | 28 + .../AlertSuppression/TestWindows.java | 28 + .../AutoBoxing/AutoBoxing.expected | 10 + .../query-tests/AutoBoxing/AutoBoxing.qlref | 1 + java/ql/test/query-tests/AutoBoxing/Test.java | 35 + .../AvoidDeprecatedCallableAccess.expected | 2 + .../AvoidDeprecatedCallableAccess.qlref | 1 + .../AvoidDeprecatedCallableAccess/Test.java | 20 + .../BadCheckOdd/BadCheckOdd.expected | 9 + .../query-tests/BadCheckOdd/BadCheckOdd.java | 70 + .../query-tests/BadCheckOdd/BadCheckOdd.qlref | 1 + .../BoxedVariable/BoxedVariable.expected | 5 + .../BoxedVariable/BoxedVariable.java | 59 + .../BoxedVariable/BoxedVariable.qlref | 1 + .../query-tests/BusyWait/BusyWait.expected | 2 + .../test/query-tests/BusyWait/BusyWait.qlref | 1 + .../test/query-tests/BusyWait/BusyWaits.java | 29 + .../CallsToRunnableRun.expected | 1 + .../CallsToRunnableRun.java | 18 + .../CallsToRunnableRun.qlref | 1 + .../CloseReader/CloseReader.expected | 2 + .../CloseReader/CloseReader.java | 108 + .../CloseReader/CloseReader.qlref | 1 + .../query-tests/CompareIdenticalValues/A.java | 45 + .../CompareIdenticalValues.expected | 18 + .../CompareIdenticalValues.qlref | 1 + .../ComplexCondition.expected | 2 + .../ComplexCondition/ComplexCondition.java | 33 + .../ComplexCondition/ComplexCondition.qlref | 1 + .../ConfusingOverloading.expected | 1 + .../ConfusingOverloading.qlref | 1 + .../TestConfusingOverloading.java | 27 + .../ConstantExpAppearsNonConstant.expected | 20 + .../ConstantExpAppearsNonConstant.qlref | 1 + .../ConstantExpAppearsNonConstant/Test.java | 81 + .../query-tests/ConstantLoopCondition/A.java | 34 + .../ConstantLoopCondition.expected | 3 + .../ConstantLoopCondition.qlref | 1 + .../ContainerSizeCmpZero.expected | 15 + .../ContainerSizeCmpZero.qlref | 1 + .../ContainerSizeCmpZero/Main.java | 119 + .../ContradictoryTypeChecks.expected | 6 + .../ContradictoryTypeChecks.qlref | 1 + .../ContradictoryTypeChecks/Test.java | 62 + .../NonAssignedFields.expected | 2 + .../NonAssignedFields/NonAssignedFields.qlref | 1 + .../NonAssignedFieldsTest.java | 16 + .../DeadCode/NonAssignedFields/Test.java | 55 + .../Declarations/BreakInSwitchCase.expected | 2 + .../Declarations/BreakInSwitchCase.qlref | 1 + .../test/query-tests/Declarations/Test.java | 31 + .../DefineEqualsWhenAddingFields/A.java | 4 + .../DefineEqualsWhenAddingFields/B.java | 3 + .../DefineEqualsWhenAddingFields.expected | 0 .../DefineEqualsWhenAddingFields.qlref | 2 + .../DelegateEq.java | 23 + .../Fragment.java | 15 + .../DefineEqualsWhenAddingFields/I.java | 3 + .../MyFragment.java | 5 + .../DefineEqualsWhenAddingFields/RefEq.java | 39 + .../EmptyInterface/EmptyInterface.expected | 3 + .../EmptyInterface/EmptyInterface.qlref | 1 + .../query-tests/EmptyInterface/IAmAlsoOK.java | 1 + .../query-tests/EmptyInterface/IAmBad.java | 1 + .../EmptyInterface/IAmBadAsWell.java | 1 + .../query-tests/EmptyInterface/IAmBadToo.java | 1 + .../EmptyInterface/IAmNonEmpty.java | 3 + .../EmptyInterface/IAmNonEmptyAsWell.java | 5 + .../EmptyInterface/IAmNonEmptyToo.java | 3 + .../query-tests/EmptyInterface/IAmOK.java | 1 + .../query-tests/EmptyInterface/IAmUseful.java | 1 + .../EqualsArray/EqualsArray.expected | 2 + .../query-tests/EqualsArray/EqualsArray.qlref | 1 + .../ql/test/query-tests/EqualsArray/Test.java | 22 + .../EqualsUsesInstanceOf.expected | 0 .../EqualsUsesInstanceOf.qlref | 1 + .../EqualsUsesInstanceOf/Test.java | 22 + java/ql/test/query-tests/Finally/Finally.java | 132 + .../Finally/FinallyMayNotComplete.expected | 7 + .../Finally/FinallyMayNotComplete.qlref | 1 + .../HashedButNoHash/HashedButNoHash.expected | 1 + .../HashedButNoHash/HashedButNoHash.qlref | 1 + .../query-tests/HashedButNoHash/Test.java | 34 + .../IgnoreExceptionalReturn.expected | 6 + .../IgnoreExceptionalReturn.qlref | 1 + .../IgnoreExceptionalReturn/Test.java | 14 + .../ImpossibleCast/ImpossibleCast.expected | 2 + .../ImpossibleCast/ImpossibleCast.qlref | 1 + .../ImpossibleCast/impossible_cast/A.java | 8 + .../InconsistentEqualsHashCode.expected | 2 + .../InconsistentEqualsHashCode.qlref | 1 + .../InconsistentEqualsHashCode/Test.java | 40 + .../InconsistentCallOnResult.expected | 3 + .../InconsistentCallOnResult.qlref | 1 + .../InconsistentOperations/Operations.java | 242 + .../ReturnValueIgnored.expected | 1 + .../ReturnValueIgnored.qlref | 1 + .../InconsistentOperations/Test2.java | 17 + .../InconsistentOperations/Test3.java | 18 + .../InconsistentOperations/Test4.java | 18 + .../InconsistentOperations/Test5.java | 18 + .../InconsistentOperations/Test6.java | 20 + .../InefficientOutputStream.expected | 1 + .../InefficientOutputStream.qlref | 1 + .../InefficientOutputStreamBad.java | 29 + .../InefficientOutputStreamGood.java | 34 + .../InnerClassCouldBeStatic/Classes.java | 263 + .../InnerClassCouldBeStatic.expected | 22 + .../InnerClassCouldBeStatic.qlref | 1 + .../InnerClassCouldBeStatic/Test.java | 10 + .../Iterable/IterableIterator.expected | 1 + .../Iterable/IterableIterator.qlref | 1 + java/ql/test/query-tests/Iterable/Test.java | 182 + .../Iterable/WrappedIterator.expected | 2 + .../Iterable/WrappedIterator.qlref | 1 + .../IteratorRemoveMayFail.expected | 2 + .../IteratorRemoveMayFail.qlref | 1 + .../IteratorRemoveMayFail/Test.java | 39 + .../Javadoc/ImpossibleJavadocThrows.expected | 2 + .../Javadoc/ImpossibleJavadocThrows.java | 34 + .../Javadoc/ImpossibleJavadocThrows.qlref | 1 + .../JdkInternalAccess.expected | 5 + .../Jdk9Compatibility/JdkInternalAccess.qlref | 1 + .../UnderscoreIdentifier.expected | 5 + .../UnderscoreIdentifier.qlref | 1 + .../p/JdkInternalAccess.java | 8 + .../p/_/UnderscoreIdentifier.java | 11 + .../LazyInitStaticField.expected | 6 + .../LazyInitStaticField.qlref | 1 + .../LazyInitStaticField/LazyInits.java | 177 + .../MissedTernaryOpportunity.expected | 6 + .../MissedTernaryOpportunity.qlref | 1 + .../MissedTernaryOpportunityTest.java | 172 + .../MissingCallToSuperClone.expected | 1 + .../MissingCallToSuperClone.qlref | 1 + .../MissingCallToSuperClone/Test.java | 21 + .../MissingInstanceofInEquals/AlsoGood.java | 65 + .../MissingInstanceofInEquals/Bad.java | 19 + .../MissingInstanceofInEquals/Good.java | 25 + .../MissingInstanceofInEquals/GoodReturn.java | 13 + .../MissingInstanceofInEquals.expected | 1 + .../MissingInstanceofInEquals.qlref | 1 + .../MissingOverrideAnnotation.expected | 1 + .../MissingOverrideAnnotation.qlref | 1 + .../MissingOverrideAnnotation/Test.java | 35 + .../test/query-tests/MissingSpaceTypo/A.java | 31 + .../MissingSpaceTypo.expected | 7 + .../MissingSpaceTypo/MissingSpaceTypo.qlref | 1 + .../MutualDependency.expected | 1 + .../MutualDependency/MutualDependency.qlref | 1 + .../onepackage/MutualDependency.java | 13 + .../otherpackage/OtherClass.java | 5 + .../Naming/ConfusingOverloading.expected | 1 + .../Naming/ConfusingOverloading.qlref | 1 + .../test/query-tests/Naming/NamingTest.java | 8 + .../NonPrivateField/NonPrivateField.expected | 8 + .../NonPrivateField/NonPrivateField.qlref | 1 + .../NonPrivateField/NonPrivateFieldTest.java | 47 + .../NonSerializableField.expected | 18 + .../NonSerializableField.qlref | 1 + .../NonSerializableFieldTest.java | 121 + .../NonSerializableInnerClass.expected | 5 + .../NonSerializableInnerClass.qlref | 1 + .../NonSerializableInnerClassTest.java | 83 + .../NonSynchronizedOverride.expected | 4 + .../NonSynchronizedOverride.qlref | 1 + .../NonSynchronizedOverride/Test.java | 44 + .../NotifyWithoutSynch.expected | 10 + .../NotifyWithoutSynch.qlref | 1 + .../query-tests/NotifyWithoutSynch/Test.java | 120 + java/ql/test/query-tests/Nullness/A.java | 307 + java/ql/test/query-tests/Nullness/B.java | 327 + java/ql/test/query-tests/Nullness/C.java | 162 + java/ql/test/query-tests/Nullness/D.java | 25 + java/ql/test/query-tests/Nullness/E.java | 25 + .../test/query-tests/Nullness/ExprDeref.java | 9 + java/ql/test/query-tests/Nullness/F.java | 34 + .../query-tests/Nullness/NullAlways.expected | 15 + .../query-tests/Nullness/NullAlways.qlref | 1 + .../Nullness/NullExprDeref.expected | 1 + .../query-tests/Nullness/NullExprDeref.qlref | 1 + .../query-tests/Nullness/NullMaybe.expected | 33 + .../test/query-tests/Nullness/NullMaybe.qlref | 1 + java/ql/test/query-tests/Nullness/options | 1 + .../PartiallyMaskedCatch.expected | 6 + .../PartiallyMaskedCatch.qlref | 1 + .../PartiallyMaskedCatchTest.java | 97 + .../PointlessForwardingMethod.expected | 1 + .../PointlessForwardingMethod.qlref | 1 + .../pointlessforwardingmethod/Test.java | 26 + .../query-tests/PrintLnArray/PrintLn.expected | 1 + .../query-tests/PrintLnArray/PrintLn.qlref | 1 + .../test/query-tests/PrintLnArray/Test.java | 8 + .../ReadOnlyContainer.expected | 2 + .../ReadOnlyContainer/ReadOnlyContainer.qlref | 1 + .../query-tests/ReadOnlyContainer/Test.java | 84 + .../ReturnValueIgnored.expected | 2 + .../ReturnValueIgnored.qlref | 1 + .../return_value_ignored/Test.java | 91 + .../SelfAssignment/SelfAssignment.expected | 1 + .../SelfAssignment/SelfAssignment.qlref | 1 + .../test/query-tests/SelfAssignment/Test.java | 23 + .../SimplifyBoolExpr.expected | 9 + .../SimplifyBoolExpr/SimplifyBoolExpr.java | 25 + .../SimplifyBoolExpr/SimplifyBoolExpr.qlref | 1 + .../SpuriousJavadocParam/Test.java | 108 + .../SpuriousJavadocParam/test.expected | 10 + .../SpuriousJavadocParam/test.qlref | 1 + .../StartInConstructor.expected | 1 + .../StartInConstructor.qlref | 1 + .../query-tests/StartInConstructor/Test.java | 33 + .../StaticArray/StaticArray.expected | 5 + .../query-tests/StaticArray/StaticArray.java | 28 + .../query-tests/StaticArray/StaticArray.qlref | 1 + .../StringComparison.expected | 3 + .../StringComparison/StringComparison.java | 32 + .../StringComparison/StringComparison.qlref | 1 + java/ql/test/query-tests/StringFormat/A.java | 88 + .../StringFormat/MissingFormatArg.expected | 19 + .../StringFormat/MissingFormatArg.qlref | 1 + .../StringFormat/UnusedFormatArg.expected | 11 + .../StringFormat/UnusedFormatArg.qlref | 1 + .../SynchSetUnsynchSet.expected | 2 + .../SynchSetUnsynchSet.qlref | 1 + .../query-tests/SynchSetUnsynchGet/Test.java | 57 + .../TypeMismatch/IncomparableEquals.expected | 2 + .../TypeMismatch/IncomparableEquals.qlref | 1 + .../TypeMismatch/RemoveTypeMismatch.expected | 5 + .../TypeMismatch/RemoveTypeMismatch.qlref | 1 + .../TypeMismatch/incomparable_equals/A.java | 9 + .../TypeMismatch/incomparable_equals/B.java | 10 + .../TypeMismatch/incomparable_equals/C.java | 10 + .../TypeMismatch/incomparable_equals/D.java | 11 + .../TypeMismatch/incomparable_equals/E.java | 22 + .../TypeMismatch/incomparable_equals/F.java | 7 + .../incomparable_equals/MyEntry.java | 10 + .../incomparable_equals/Test.java | 13 + .../TypeMismatch/remove_type_mismatch/A.java | 45 + .../TypeMismatch/remove_type_mismatch/B.java | 11 + java/ql/test/query-tests/UnreadLocal/A.java | 76 + .../UnreadLocal/DeadStoreOfLocal.expected | 5 + .../UnreadLocal/DeadStoreOfLocal.qlref | 1 + .../DeadStoreOfLocalUnread.expected | 2 + .../UnreadLocal/DeadStoreOfLocalUnread.qlref | 1 + .../UnreadLocal/UnreadLocal.expected | 2 + .../query-tests/UnreadLocal/UnreadLocal.qlref | 1 + .../UnreadLocal/ImplicitReads.java | 42 + .../UnreadLocal/UnreadLocal/UnreadLocals.java | 38 + .../UnreleasedLock/UnreleasedLock.expected | 5 + .../UnreleasedLock/UnreleasedLock.java | 98 + .../UnreleasedLock/UnreleasedLock.qlref | 1 + .../query-tests/UnusedField/SomeFields.java | 33 + .../UnusedField/SomeFieldsInSerializable.java | 39 + .../UnusedField/UnusedField.expected | 6 + .../query-tests/UnusedField/UnusedField.qlref | 1 + .../query-tests/UseBraces/UseBraces.expected | 14 + .../test/query-tests/UseBraces/UseBraces.java | 198 + .../query-tests/UseBraces/UseBraces.qlref | 1 + .../query-tests/UselessComparisonTest/A.java | 130 + .../UselessComparisonTest/Test.java | 35 + .../UselessComparisonTest.expected | 25 + .../UselessComparisonTest.qlref | 1 + .../test/query-tests/UselessNullCheck/A.java | 47 + .../UselessNullCheck.expected | 7 + .../UselessNullCheck/UselessNullCheck.qlref | 1 + java/ql/test/query-tests/UselessUpcast/A.java | 9 + .../test/query-tests/UselessUpcast/Test.java | 40 + .../test/query-tests/UselessUpcast/Test2.java | 11 + .../UselessUpcast/UselessUpcast.expected | 3 + .../UselessUpcast/UselessUpcast.qlref | 1 + .../WhitespaceContradictsPrecedence.expected | 1 + .../WhitespaceContradictsPrecedence.java | 29 + .../WhitespaceContradictsPrecedence.qlref | 1 + .../WriteOnlyContainer/CollectionTest.java | 52 + .../WriteOnlyContainer/MapTest.java | 52 + .../WriteOnlyContainer.expected | 2 + .../WriteOnlyContainer.qlref | 1 + .../query-tests/WrongNanComparison/Test.java | 6 + .../WrongNanComparison.expected | 2 + .../WrongNanComparison.qlref | 1 + .../DeadCallable/DeadCallable.expected | 5 + .../dead-code/DeadCallable/DeadCallable.qlref | 1 + .../dead-code/DeadCallable/Main.java | 17 + .../dead-code/DeadClass/AnnotationTest.java | 11 + .../dead-code/DeadClass/DeadClass.expected | 5 + .../dead-code/DeadClass/DeadClass.qlref | 1 + .../dead-code/DeadClass/DeadEnumTest.java | 13 + .../DeadClass/ExternalDeadCodeCycle.java | 25 + .../dead-code/DeadClass/ExternalDeadRoot.java | 23 + .../DeadClass/InternalDeadCodeCycle.java | 13 + .../dead-code/DeadClass/NamespaceTest.java | 51 + .../DeadEnumConstant.expected | 2 + .../DeadEnumConstant/DeadEnumConstant.qlref | 1 + .../DeadEnumConstantTest.java | 34 + .../DeadField/AnnotationValueTest.java | 43 + .../DeadField/AnnotationValueUtil.java | 29 + .../dead-code/DeadField/BasicTest.java | 41 + .../dead-code/DeadField/DeadField.expected | 9 + .../dead-code/DeadField/DeadField.qlref | 1 + .../dead-code/DeadField/ReflectionTest.java | 28 + .../DeadMethod/ArbitraryXMLLiveness.java | 9 + .../dead-code/DeadMethod/DeadMethod.expected | 10 + .../dead-code/DeadMethod/DeadMethod.qlref | 1 + .../DeadMethod/DefaultConstructorTest.java | 2 + .../DeadMethod/InternalDeadCodeCycle.java | 14 + .../dead-code/DeadMethod/JMXTest.java | 32 + .../dead-code/DeadMethod/JaxbTest.java | 158 + .../DeadMethod/ReflectionMethodTest.java | 41 + .../dead-code/DeadMethod/ReflectionTest.java | 41 + .../DeadMethod/SuppressedConstructorTest.java | 37 + .../dead-code/DeadMethod/arbitrary.xml | 4 + .../dead-code/UselessParameter/Test.java | 102 + .../UselessParameter.expected | 1 + .../UselessParameter/UselessParameter.qlref | 1 + .../ql/test/query-tests/definitions/Test.java | 35 + .../definitions/definitions.expected | 11 + .../query-tests/definitions/definitions.qlref | 1 + .../lgtm-example-queries/Test.java | 17 + .../lgtm-example-queries/arrayaccess.expected | 1 + .../lgtm-example-queries/arrayaccess.ql | 16 + .../returnstatement.expected | 1 + .../lgtm-example-queries/returnstatement.ql | 13 + .../voidreturntype.expected | 1 + .../lgtm-example-queries/voidreturntype.ql | 16 + .../.m2/repository/readme.txt | 1 + .../semmle-test/semmle-test/1.0/pom.xml | 14 + .../semmle-test/1.0/semmle-test-1.0.jar | 1 + .../DependencyVersions.expected | 5 + .../maven-dependencies/DependencyVersions.ql | 4 + .../maven-dependencies/MavenDeps.expected | 1 + .../maven-dependencies/MavenDeps.ql | 11 + .../maven-dependencies/MavenPoms.expected | 4 + .../maven-dependencies/MavenPoms.ql | 10 + .../UnusedMavenDependencyBinary.expected | 2 + .../UnusedMavenDependencyBinary.qlref | 1 + .../UnusedMavenDependencySource.expected | 1 + .../UnusedMavenDependencySource.qlref | 1 + .../another-project/pom.xml | 16 + .../src/main/java/Library.java | 8 + .../maven-dependencies/my-project/pom.xml | 33 + .../my-project/src/main/java/Test.java | 7 + .../query-tests/maven-dependencies/pom.xml | 31 + .../CWE-022/semmle/tests/TaintedPath.expected | 3 + .../CWE-022/semmle/tests/TaintedPath.qlref | 1 + .../security/CWE-022/semmle/tests/Test.java | 71 + .../CWE-079/semmle/tests/XSS.expected | 4 + .../security/CWE-079/semmle/tests/XSS.java | 64 + .../security/CWE-079/semmle/tests/XSS.qlref | 1 + .../security/CWE-079/semmle/tests/options | 1 + .../semmle/examples/SqlTaintedLocal.expected | 4 + .../semmle/examples/SqlTaintedLocal.qlref | 1 + .../semmle/examples/SqlUnescaped.expected | 6 + .../semmle/examples/SqlUnescaped.qlref | 1 + .../CWE-089/semmle/examples/Test.java | 198 + .../examples/ValidatedVariable.expected | 1 + .../semmle/examples/ValidatedVariable.ql | 4 + .../CWE-089/semmle/examples/Validation.java | 14 + .../semmle/examples/controlledString.expected | 95 + .../semmle/examples/controlledString.ql | 9 + .../semmle/examples/endsInQuote.expected | 13 + .../CWE-089/semmle/examples/endsInQuote.ql | 8 + .../semmle/examples/getAnAppend.expected | 9 + .../CWE-089/semmle/examples/getAnAppend.ql | 12 + .../semmle/examples/getToStringCall.expected | 3 + .../semmle/examples/getToStringCall.ql | 10 + .../CWE-089/semmle/examples/sbQuery.expected | 3 + .../CWE-089/semmle/examples/sbQuery.ql | 14 + .../semmle/examples/taintedString.expected | 32 + .../CWE-089/semmle/examples/taintedString.ql | 14 + .../semmle/examples/validationMethod.expected | 1 + .../semmle/examples/validationMethod.ql | 5 + .../semmle/tests/ResponseSplitting.expected | 3 + .../semmle/tests/ResponseSplitting.java | 45 + .../semmle/tests/ResponseSplitting.qlref | 1 + .../security/CWE-113/semmle/tests/options | 1 + ...nOfArrayConstructionCodeSpecified.expected | 1 + ...tionOfArrayConstructionCodeSpecified.qlref | 1 + ...alidationOfArrayConstructionLocal.expected | 2 + ...erValidationOfArrayConstructionLocal.qlref | 1 + ...lidationOfArrayIndexCodeSpecified.expected | 1 + ...rValidationOfArrayIndexCodeSpecified.qlref | 1 + ...properValidationOfArrayIndexLocal.expected | 1 + .../ImproperValidationOfArrayIndexLocal.qlref | 1 + .../security/CWE-129/semmle/tests/Test.java | 104 + .../ExternallyControlledFormatString.expected | 1 + .../ExternallyControlledFormatString.qlref | 1 + ...rnallyControlledFormatStringLocal.expected | 12 + ...xternallyControlledFormatStringLocal.qlref | 1 + .../security/CWE-134/semmle/tests/Test.java | 42 + .../security/CWE-134/semmle/tests/options | 1 + .../semmle/tests/ArithmeticTainted.java | 141 + .../tests/ArithmeticTaintedLocal.expected | 9 + .../semmle/tests/ArithmeticTaintedLocal.qlref | 1 + .../tests/ArithmeticUncontrolled.expected | 2 + .../semmle/tests/ArithmeticUncontrolled.qlref | 1 + .../ArithmeticWithExtremeValues.expected | 8 + .../tests/ArithmeticWithExtremeValues.qlref | 1 + .../security/CWE-190/semmle/tests/Holder.java | 19 + .../semmle/tests/InformationLoss.expected | 2 + .../semmle/tests/InformationLoss.qlref | 1 + .../semmle/tests/IntMultToLong.expected | 4 + .../CWE-190/semmle/tests/IntMultToLong.qlref | 1 + .../security/CWE-190/semmle/tests/Test.java | 251 + .../semmle/tests/StackTraceExposure.expected | 4 + .../semmle/tests/StackTraceExposure.qlref | 1 + .../security/CWE-209/semmle/tests/Test.java | 74 + .../security/CWE-209/semmle/tests/options | 1 + .../tests/CleartextStorageClass.expected | 4 + .../semmle/tests/CleartextStorageClass.qlref | 1 + .../tests/CleartextStorageCookie.expected | 3 + .../semmle/tests/CleartextStorageCookie.qlref | 1 + .../tests/CleartextStorageProperties.expected | 2 + .../tests/CleartextStorageProperties.qlref | 1 + .../CWE-311/CWE-312/semmle/tests/Test.java | 143 + .../CWE-311/CWE-312/semmle/tests/options | 1 + .../CWE-319/semmle/tests/HttpsUrls.expected | 4 + .../CWE-319/semmle/tests/HttpsUrls.qlref | 1 + .../CWE-311/CWE-319/semmle/tests/Test.java | 141 + .../tests/UseSSLSocketFactories.expected | 2 + .../semmle/tests/UseSSLSocketFactories.qlref | 1 + .../semmle/tests/InsecureCookie.expected | 1 + .../CWE-614/semmle/tests/InsecureCookie.qlref | 1 + .../CWE-311/CWE-614/semmle/tests/Test.java | 28 + .../CWE-311/CWE-614/semmle/tests/options | 1 + .../tests/BrokenCryptoAlgorithm.expected | 2 + .../semmle/tests/BrokenCryptoAlgorithm.qlref | 1 + .../tests/MaybeBrokenCryptoAlgorithm.expected | 1 + .../tests/MaybeBrokenCryptoAlgorithm.qlref | 1 + .../security/CWE-327/semmle/tests/Test.java | 53 + .../semmle/tests/PredictableSeed.expected | 4 + .../semmle/tests/PredictableSeed.qlref | 1 + .../security/CWE-335/semmle/tests/Test.java | 61 + .../CWE-367/semmle/tests/TOCTOURace.expected | 4 + .../CWE-367/semmle/tests/TOCTOURace.qlref | 1 + .../security/CWE-367/semmle/tests/Test.java | 128 + .../CWE-421/semmle/SocketAuthRace.expected | 3 + .../CWE-421/semmle/SocketAuthRace.qlref | 1 + .../security/CWE-421/semmle/Test.java | 77 + .../security/CWE-421/semmle/options | 1 + .../test/query-tests/security/CWE-502/A.java | 97 + .../CWE-502/TestMessageBodyReader.java | 28 + .../CWE-502/UnsafeDeserialization.expected | 18 + .../CWE-502/UnsafeDeserialization.qlref | 1 + .../test/query-tests/security/CWE-502/options | 1 + .../CWE-601/semmle/tests/UrlRedirect.expected | 4 + .../CWE-601/semmle/tests/UrlRedirect.java | 48 + .../CWE-601/semmle/tests/UrlRedirect.qlref | 1 + .../security/CWE-601/semmle/tests/options | 1 + .../CWE-611/DocumentBuilderTests.java | 132 + .../security/CWE-611/SAXBuilderTests.java | 22 + .../security/CWE-611/SAXParserTests.java | 75 + .../security/CWE-611/SAXReaderTests.java | 63 + .../security/CWE-611/SAXSourceTests.java | 41 + .../security/CWE-611/SchemaTests.java | 47 + .../security/CWE-611/SimpleXMLTests.java | 155 + .../security/CWE-611/TransformerTests.java | 143 + .../security/CWE-611/UnmarshallerTests.java | 30 + .../security/CWE-611/XMLReaderTests.java | 102 + .../CWE-611/XPathExpressionTests.java | 29 + .../query-tests/security/CWE-611/XXE.expected | 96 + .../query-tests/security/CWE-611/XXE.qlref | 1 + .../CWE-611/XmlInputFactoryTests.java | 58 + .../test/query-tests/security/CWE-611/options | 1 + .../PotentiallyDangerousFunction.expected | 1 + .../tests/PotentiallyDangerousFunction.qlref | 1 + .../security/CWE-676/semmle/tests/Test.java | 16 + .../tests/NumericCastTaintedLocal.expected | 1 + .../tests/NumericCastTaintedLocal.qlref | 1 + .../security/CWE-681/semmle/tests/Test.java | 33 + .../ReadingFromWorldWritableFile.expected | 3 + .../tests/ReadingFromWorldWritableFile.qlref | 1 + .../security/CWE-732/semmle/tests/Test.java | 41 + .../CWE-798/semmle/tests/CredentialsTest.java | 20 + .../semmle/tests/FileCredentialTest.java | 39 + .../HardcodedCredentialsApiCall.expected | 18 + .../tests/HardcodedCredentialsApiCall.qlref | 1 + .../HardcodedCredentialsComparison.expected | 1 + .../HardcodedCredentialsComparison.qlref | 1 + .../HardcodedCredentialsSourceCall.expected | 2 + .../HardcodedCredentialsSourceCall.qlref | 1 + .../tests/HardcodedPasswordField.expected | 1 + .../semmle/tests/HardcodedPasswordField.qlref | 1 + .../security/CWE-798/semmle/tests/Test.java | 38 + .../security/CWE-798/semmle/tests/User.java | 10 + .../semmle/tests/ConditionalBypass.expected | 6 + .../semmle/tests/ConditionalBypass.qlref | 1 + .../tests/TaintedPermissionsCheck.expected | 1 + .../tests/TaintedPermissionsCheck.qlref | 1 + .../security/CWE-807/semmle/tests/Test.java | 131 + .../security/CWE-807/semmle/tests/options | 1 + .../tests/LockOrderInconsistency.expected | 5 + .../semmle/tests/LockOrderInconsistency.qlref | 1 + .../semmle/tests/MethodAccessLockOrder.java | 35 + .../semmle/tests/ReentrantLockOrder.java | 41 + .../tests/SynchronizedStmtLockOrder.java | 33 + .../semmle/tests/InfiniteLoop.expected | 1 + .../CWE-835/semmle/tests/InfiniteLoop.java | 13 + .../CWE-835/semmle/tests/InfiniteLoop.qlref | 1 + .../org/apache/commons/exec/CommandLine.java | 79 + .../apache/commons/exec/DefaultExecutor.java | 42 + .../stubs/dom4j-2.1.1/org/dom4j/Document.java | 54 + .../dom4j-2.1.1/org/dom4j/io/SAXReader.java | 101 + .../j2objc/security/IosRSASignature.java | 339 + .../stubs/jdom-1.1.3/org/jdom/Document.java | 67 + .../jdom-1.1.3/org/jdom/input/SAXBuilder.java | 79 + .../javax/ws/rs/core/MediaType.java | 30 + .../javax/ws/rs/core/MultivaluedMap.java | 33 + .../javax/ws/rs/ext/MessageBodyReader.java | 45 + .../stubs/junit-4.11/org/junit/Assert.java | 153 + .../stubs/junit-jupiter-api-5.2.0/LICENSE.md | 98 + .../org/junit/jupiter/api/Test.java | 29 + .../com/esotericsoftware/kryo/Kryo.java | 50 + .../esotericsoftware/kryo/Registration.java | 29 + .../com/esotericsoftware/kryo/io/Input.java | 33 + .../javax/servlet/RequestDispatcher.java | 33 + .../javax/servlet/Servlet.java | 35 + .../javax/servlet/ServletConfig.java | 33 + .../javax/servlet/ServletContext.java | 57 + .../javax/servlet/ServletException.java | 27 + .../javax/servlet/ServletInputStream.java | 34 + .../javax/servlet/ServletOutputStream.java | 62 + .../javax/servlet/ServletRequest.java | 62 + .../javax/servlet/ServletResponse.java | 46 + .../javax/servlet/http/Cookie.java | 84 + .../javax/servlet/http/HttpServlet.java | 62 + .../servlet/http/HttpServletRequest.java | 55 + .../servlet/http/HttpServletResponse.java | 89 + .../javax/servlet/http/HttpSession.java | 47 + .../servlet/http/HttpSessionContext.java | 34 + .../org/apache/shiro/SecurityUtils.java | 36 + .../org/apache/shiro/subject/Subject.java | 36 + .../org/simpleframework/xml/Serializer.java | 80 + .../simpleframework/xml/core/Persister.java | 82 + .../xml/stream/DocumentProvider.java | 35 + .../simpleframework/xml/stream/Formatter.java | 35 + .../xml/stream/NodeBuilder.java | 35 + .../simpleframework/xml/stream/Provider.java | 35 + .../xml/stream/StreamProvider.java | 35 + .../org/yaml/snakeyaml/Yaml.java | 80 + .../constructor/BaseConstructor.java | 28 + .../snakeyaml/constructor/Constructor.java | 33 + .../constructor/SafeConstructor.java | 30 + .../org/yaml/snakeyaml/events/Event.java | 26 + .../com/thoughtworks/xstream/XStream.java | 68 + 2319 files changed, 134386 insertions(+) create mode 100644 java/ql/src/.project create mode 100644 java/ql/src/.qlpath create mode 100644 java/ql/src/.settings/org.eclipse.jdt.core.prefs create mode 100644 java/ql/src/.vs/VSWorkspaceSettings.json create mode 100644 java/ql/src/Advisory/Declarations/MissingOverrideAnnotation.java create mode 100644 java/ql/src/Advisory/Declarations/MissingOverrideAnnotation.qhelp create mode 100644 java/ql/src/Advisory/Declarations/MissingOverrideAnnotation.ql create mode 100644 java/ql/src/Advisory/Declarations/NonFinalImmutableField.qhelp create mode 100644 java/ql/src/Advisory/Declarations/NonFinalImmutableField.ql create mode 100644 java/ql/src/Advisory/Declarations/NonPrivateField.qhelp create mode 100644 java/ql/src/Advisory/Declarations/NonPrivateField.ql create mode 100644 java/ql/src/Advisory/Deprecated Code/AvoidDeprecatedCallableAccess.qhelp create mode 100644 java/ql/src/Advisory/Deprecated Code/AvoidDeprecatedCallableAccess.ql create mode 100644 java/ql/src/Advisory/Documentation/ImpossibleJavadocThrows.java create mode 100644 java/ql/src/Advisory/Documentation/ImpossibleJavadocThrows.qhelp create mode 100644 java/ql/src/Advisory/Documentation/ImpossibleJavadocThrows.ql create mode 100644 java/ql/src/Advisory/Documentation/ImpossibleJavadocThrowsFix.java create mode 100644 java/ql/src/Advisory/Documentation/JavadocCommon.qll create mode 100644 java/ql/src/Advisory/Documentation/MissingJavadocMethods.java create mode 100644 java/ql/src/Advisory/Documentation/MissingJavadocMethods.qhelp create mode 100644 java/ql/src/Advisory/Documentation/MissingJavadocMethods.ql create mode 100644 java/ql/src/Advisory/Documentation/MissingJavadocParameters.qhelp create mode 100644 java/ql/src/Advisory/Documentation/MissingJavadocParameters.ql create mode 100644 java/ql/src/Advisory/Documentation/MissingJavadocReturnValues.qhelp create mode 100644 java/ql/src/Advisory/Documentation/MissingJavadocReturnValues.ql create mode 100644 java/ql/src/Advisory/Documentation/MissingJavadocThrows.qhelp create mode 100644 java/ql/src/Advisory/Documentation/MissingJavadocThrows.ql create mode 100644 java/ql/src/Advisory/Documentation/MissingJavadocTypes.java create mode 100644 java/ql/src/Advisory/Documentation/MissingJavadocTypes.qhelp create mode 100644 java/ql/src/Advisory/Documentation/MissingJavadocTypes.ql create mode 100644 java/ql/src/Advisory/Documentation/SpuriousJavadocParam.java create mode 100644 java/ql/src/Advisory/Documentation/SpuriousJavadocParam.qhelp create mode 100644 java/ql/src/Advisory/Documentation/SpuriousJavadocParam.ql create mode 100644 java/ql/src/Advisory/Java Objects/AvoidCloneMethodAccess.qhelp create mode 100644 java/ql/src/Advisory/Java Objects/AvoidCloneMethodAccess.ql create mode 100644 java/ql/src/Advisory/Java Objects/AvoidCloneOverride.qhelp create mode 100644 java/ql/src/Advisory/Java Objects/AvoidCloneOverride.ql create mode 100644 java/ql/src/Advisory/Java Objects/AvoidCloneableInterface.java create mode 100644 java/ql/src/Advisory/Java Objects/AvoidCloneableInterface.qhelp create mode 100644 java/ql/src/Advisory/Java Objects/AvoidCloneableInterface.ql create mode 100644 java/ql/src/Advisory/Java Objects/AvoidFinalizeOverride.qhelp create mode 100644 java/ql/src/Advisory/Java Objects/AvoidFinalizeOverride.ql create mode 100644 java/ql/src/Advisory/Naming/NamingConventionsCommon.qll create mode 100644 java/ql/src/Advisory/Naming/NamingConventionsConstants.qhelp create mode 100644 java/ql/src/Advisory/Naming/NamingConventionsConstants.ql create mode 100644 java/ql/src/Advisory/Naming/NamingConventionsMethods.qhelp create mode 100644 java/ql/src/Advisory/Naming/NamingConventionsMethods.ql create mode 100644 java/ql/src/Advisory/Naming/NamingConventionsPackages.qhelp create mode 100644 java/ql/src/Advisory/Naming/NamingConventionsPackages.ql create mode 100644 java/ql/src/Advisory/Naming/NamingConventionsRefTypes.qhelp create mode 100644 java/ql/src/Advisory/Naming/NamingConventionsRefTypes.ql create mode 100644 java/ql/src/Advisory/Naming/NamingConventionsVariables.qhelp create mode 100644 java/ql/src/Advisory/Naming/NamingConventionsVariables.ql create mode 100644 java/ql/src/Advisory/Statements/MissingDefaultInSwitch.java create mode 100644 java/ql/src/Advisory/Statements/MissingDefaultInSwitch.qhelp create mode 100644 java/ql/src/Advisory/Statements/MissingDefaultInSwitch.ql create mode 100644 java/ql/src/Advisory/Statements/MissingDefaultInSwitchGood.java create mode 100644 java/ql/src/Advisory/Statements/OneStatementPerLine.qhelp create mode 100644 java/ql/src/Advisory/Statements/OneStatementPerLine.ql create mode 100644 java/ql/src/Advisory/Statements/TerminateIfElseIfWithElse.java create mode 100644 java/ql/src/Advisory/Statements/TerminateIfElseIfWithElse.qhelp create mode 100644 java/ql/src/Advisory/Statements/TerminateIfElseIfWithElse.ql create mode 100644 java/ql/src/Advisory/Statements/TerminateIfElseIfWithElseGood.java create mode 100644 java/ql/src/Advisory/Types/GenericsConstructor.qhelp create mode 100644 java/ql/src/Advisory/Types/GenericsConstructor.ql create mode 100644 java/ql/src/Advisory/Types/GenericsReturnType.qhelp create mode 100644 java/ql/src/Advisory/Types/GenericsReturnType.ql create mode 100644 java/ql/src/Advisory/Types/GenericsVariable.qhelp create mode 100644 java/ql/src/Advisory/Types/GenericsVariable.ql create mode 100644 java/ql/src/Advisory/Types/Generics_Common.java create mode 100644 java/ql/src/Advisory/Types/Generics_Common.qhelp create mode 100644 java/ql/src/Advisory/Types/Generics_CommonGood.java create mode 100644 java/ql/src/AlertSuppression.ql create mode 100644 java/ql/src/Architecture/Dependencies/MutualDependency.java create mode 100644 java/ql/src/Architecture/Dependencies/MutualDependency.qhelp create mode 100644 java/ql/src/Architecture/Dependencies/MutualDependency.ql create mode 100644 java/ql/src/Architecture/Dependencies/MutualDependencyFix.java create mode 100644 java/ql/src/Architecture/Dependencies/UnusedMavenDependencies.qll create mode 100644 java/ql/src/Architecture/Dependencies/UnusedMavenDependency.qhelp create mode 100644 java/ql/src/Architecture/Dependencies/UnusedMavenDependencyBinary.qhelp create mode 100644 java/ql/src/Architecture/Dependencies/UnusedMavenDependencyBinary.ql create mode 100644 java/ql/src/Architecture/Dependencies/UnusedMavenDependencySource.qhelp create mode 100644 java/ql/src/Architecture/Dependencies/UnusedMavenDependencySource.ql create mode 100644 java/ql/src/Architecture/Refactoring Opportunities/DeeplyNestedClass.java create mode 100644 java/ql/src/Architecture/Refactoring Opportunities/DeeplyNestedClass.qhelp create mode 100644 java/ql/src/Architecture/Refactoring Opportunities/DeeplyNestedClass.ql create mode 100644 java/ql/src/Architecture/Refactoring Opportunities/DeeplyNestedClassFix.java create mode 100644 java/ql/src/Architecture/Refactoring Opportunities/FeatureEnvy.java create mode 100644 java/ql/src/Architecture/Refactoring Opportunities/FeatureEnvy.qhelp create mode 100644 java/ql/src/Architecture/Refactoring Opportunities/FeatureEnvy.ql create mode 100644 java/ql/src/Architecture/Refactoring Opportunities/HubClasses.qhelp create mode 100644 java/ql/src/Architecture/Refactoring Opportunities/HubClasses.ql create mode 100644 java/ql/src/Architecture/Refactoring Opportunities/InappropriateIntimacy.qhelp create mode 100644 java/ql/src/Architecture/Refactoring Opportunities/InappropriateIntimacy.ql create mode 100644 java/ql/src/Compatibility/JDK9/JdkInternalAccess.qhelp create mode 100644 java/ql/src/Compatibility/JDK9/JdkInternalAccess.ql create mode 100644 java/ql/src/Compatibility/JDK9/JdkInternals.qll create mode 100644 java/ql/src/Compatibility/JDK9/JdkInternalsReplacement.qll create mode 100644 java/ql/src/Compatibility/JDK9/UnderscoreIdentifier.qhelp create mode 100644 java/ql/src/Compatibility/JDK9/UnderscoreIdentifier.ql create mode 100644 java/ql/src/Complexity/BlockWithTooManyStatements.qhelp create mode 100644 java/ql/src/Complexity/BlockWithTooManyStatements.ql create mode 100644 java/ql/src/Complexity/ComplexCondition.java create mode 100644 java/ql/src/Complexity/ComplexCondition.qhelp create mode 100644 java/ql/src/Complexity/ComplexCondition.ql create mode 100644 java/ql/src/Complexity/ComplexConditionGood.java create mode 100644 java/ql/src/DeadCode/DeadClass.java create mode 100644 java/ql/src/DeadCode/DeadClass.qhelp create mode 100644 java/ql/src/DeadCode/DeadClass.ql create mode 100644 java/ql/src/DeadCode/DeadCodeDetails.qhelp create mode 100644 java/ql/src/DeadCode/DeadCodeExtraEntryPoints.qhelp create mode 100644 java/ql/src/DeadCode/DeadCodeReferences.qhelp create mode 100644 java/ql/src/DeadCode/DeadCodeSummary.qhelp create mode 100644 java/ql/src/DeadCode/DeadEnumConstant.java create mode 100644 java/ql/src/DeadCode/DeadEnumConstant.qhelp create mode 100644 java/ql/src/DeadCode/DeadEnumConstant.ql create mode 100644 java/ql/src/DeadCode/DeadField.java create mode 100644 java/ql/src/DeadCode/DeadField.qhelp create mode 100644 java/ql/src/DeadCode/DeadField.ql create mode 100644 java/ql/src/DeadCode/DeadFieldSerialized.java create mode 100644 java/ql/src/DeadCode/DeadFieldWrittenTo.java create mode 100644 java/ql/src/DeadCode/DeadMethod.java create mode 100644 java/ql/src/DeadCode/DeadMethod.qhelp create mode 100644 java/ql/src/DeadCode/DeadMethod.ql create mode 100644 java/ql/src/DeadCode/DeadMethodTest.java create mode 100644 java/ql/src/DeadCode/FLinesOfDeadCode.qhelp create mode 100644 java/ql/src/DeadCode/FLinesOfDeadCode.ql create mode 100644 java/ql/src/DeadCode/NamespaceClass.java create mode 100644 java/ql/src/DeadCode/NamespaceClass2.java create mode 100644 java/ql/src/DeadCode/UselessParameter.java create mode 100644 java/ql/src/DeadCode/UselessParameter.qhelp create mode 100644 java/ql/src/DeadCode/UselessParameter.ql create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbContainerInterference.qhelp create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbContainerInterference.ql create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbFileIO.qhelp create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbFileIO.ql create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbGraphics.qhelp create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbGraphics.ql create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbNative.qhelp create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbNative.ql create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbReflection.qhelp create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbReflection.ql create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbSecurityConfiguration.qhelp create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbSecurityConfiguration.ql create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbSerialization.qhelp create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbSerialization.ql create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbSetSocketOrUrlFactory.qhelp create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbSetSocketOrUrlFactory.ql create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbSocketAsServer.qhelp create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbSocketAsServer.ql create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbStaticFieldNonFinal.qhelp create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbStaticFieldNonFinal.ql create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbSynchronization.qhelp create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbSynchronization.ql create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbThis.qhelp create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbThis.ql create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbThreads.qhelp create mode 100644 java/ql/src/Frameworks/JavaEE/EJB/EjbThreads.ql create mode 100644 java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/MissingParentBean.qhelp create mode 100644 java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/MissingParentBean.ql create mode 100644 java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/MissingParentBean.xml create mode 100644 java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/MissingParentBeanGood.xml create mode 100644 java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/TooManyBeans.qhelp create mode 100644 java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/TooManyBeans.ql create mode 100644 java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/TooManyBeans.xml create mode 100644 java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UnusedBean.java create mode 100644 java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UnusedBean.qhelp create mode 100644 java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UnusedBean.ql create mode 100644 java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UnusedBean.xml create mode 100644 java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UselessPropertyOverride.qhelp create mode 100644 java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UselessPropertyOverride.ql create mode 100644 java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UselessPropertyOverride.xml create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/AvoidAutowiring.qhelp create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/AvoidAutowiring.ql create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/AvoidAutowiring.xml create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/DontUseConstructorArgIndex.qhelp create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/DontUseConstructorArgIndex.ql create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/DontUseConstructorArgIndex.xml create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/ImportsFirst.qhelp create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/ImportsFirst.ql create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/ImportsFirst.xml create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/NoBeanDescription.qhelp create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/NoBeanDescription.ql create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/NoBeanDescription.xml create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/ParentShouldNotUseAbstractClass.qhelp create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/ParentShouldNotUseAbstractClass.ql create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/ParentShouldNotUseAbstractClass.xml create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/UseIdInsteadOfName.qhelp create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/UseIdInsteadOfName.ql create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/UseIdInsteadOfName.xml create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/UseLocalRef.qhelp create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/UseLocalRef.ql create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/UseLocalRef.xml create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjection.java create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjection.qhelp create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjection.ql create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjection.xml create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjectionGood.java create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjectionGood.xml create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/UseShortcutForms.qhelp create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/UseShortcutForms.ql create mode 100644 java/ql/src/Frameworks/Spring/Violations of Best Practice/UseShortcutForms.xml create mode 100644 java/ql/src/Frameworks/Spring/XML Configuration Errors/MissingSetters.java create mode 100644 java/ql/src/Frameworks/Spring/XML Configuration Errors/MissingSetters.qhelp create mode 100644 java/ql/src/Frameworks/Spring/XML Configuration Errors/MissingSetters.ql create mode 100644 java/ql/src/Frameworks/Spring/XML Configuration Errors/MissingSetters.xml create mode 100644 java/ql/src/Language Abuse/CastThisToTypeParameter.java create mode 100644 java/ql/src/Language Abuse/CastThisToTypeParameter.qhelp create mode 100644 java/ql/src/Language Abuse/CastThisToTypeParameter.ql create mode 100644 java/ql/src/Language Abuse/CastThisToTypeParameterFix.java create mode 100644 java/ql/src/Language Abuse/ChainedInstanceof.java create mode 100644 java/ql/src/Language Abuse/ChainedInstanceof.qhelp create mode 100644 java/ql/src/Language Abuse/ChainedInstanceof.ql create mode 100644 java/ql/src/Language Abuse/DubiousDowncastOfThis.java create mode 100644 java/ql/src/Language Abuse/DubiousDowncastOfThis.qhelp create mode 100644 java/ql/src/Language Abuse/DubiousDowncastOfThis.ql create mode 100644 java/ql/src/Language Abuse/DubiousTypeTestOfThis.java create mode 100644 java/ql/src/Language Abuse/DubiousTypeTestOfThis.qhelp create mode 100644 java/ql/src/Language Abuse/DubiousTypeTestOfThis.ql create mode 100644 java/ql/src/Language Abuse/EmptyStatement.java create mode 100644 java/ql/src/Language Abuse/EmptyStatement.qhelp create mode 100644 java/ql/src/Language Abuse/EmptyStatement.ql create mode 100644 java/ql/src/Language Abuse/EnumIdentifier.java create mode 100644 java/ql/src/Language Abuse/EnumIdentifier.qhelp create mode 100644 java/ql/src/Language Abuse/EnumIdentifier.ql create mode 100644 java/ql/src/Language Abuse/ImplementsAnnotation.java create mode 100644 java/ql/src/Language Abuse/ImplementsAnnotation.qhelp create mode 100644 java/ql/src/Language Abuse/ImplementsAnnotation.ql create mode 100644 java/ql/src/Language Abuse/ImplementsAnnotationGood.java create mode 100644 java/ql/src/Language Abuse/IterableClass.qll create mode 100644 java/ql/src/Language Abuse/IterableIterator.qhelp create mode 100644 java/ql/src/Language Abuse/IterableIterator.ql create mode 100644 java/ql/src/Language Abuse/IterableIteratorBad.java create mode 100644 java/ql/src/Language Abuse/IterableIteratorGood1.java create mode 100644 java/ql/src/Language Abuse/IterableIteratorGood2.java create mode 100644 java/ql/src/Language Abuse/IterableOverview.qhelp create mode 100644 java/ql/src/Language Abuse/MissedTernaryOpportunity.java create mode 100644 java/ql/src/Language Abuse/MissedTernaryOpportunity.qhelp create mode 100644 java/ql/src/Language Abuse/MissedTernaryOpportunity.ql create mode 100644 java/ql/src/Language Abuse/OverridePackagePrivate.java create mode 100644 java/ql/src/Language Abuse/OverridePackagePrivate.qhelp create mode 100644 java/ql/src/Language Abuse/OverridePackagePrivate.ql create mode 100644 java/ql/src/Language Abuse/TypeVarExtendsFinalType.java create mode 100644 java/ql/src/Language Abuse/TypeVarExtendsFinalType.qhelp create mode 100644 java/ql/src/Language Abuse/TypeVarExtendsFinalType.ql create mode 100644 java/ql/src/Language Abuse/TypeVariableHidesType.java create mode 100644 java/ql/src/Language Abuse/TypeVariableHidesType.qhelp create mode 100644 java/ql/src/Language Abuse/TypeVariableHidesType.ql create mode 100644 java/ql/src/Language Abuse/UselessNullCheck.qhelp create mode 100644 java/ql/src/Language Abuse/UselessNullCheck.ql create mode 100644 java/ql/src/Language Abuse/UselessNullCheckBad.java create mode 100644 java/ql/src/Language Abuse/UselessTypeTest.java create mode 100644 java/ql/src/Language Abuse/UselessTypeTest.qhelp create mode 100644 java/ql/src/Language Abuse/UselessTypeTest.ql create mode 100644 java/ql/src/Language Abuse/UselessUpcast.java create mode 100644 java/ql/src/Language Abuse/UselessUpcast.qhelp create mode 100644 java/ql/src/Language Abuse/UselessUpcast.ql create mode 100644 java/ql/src/Language Abuse/WrappedIterator.qhelp create mode 100644 java/ql/src/Language Abuse/WrappedIterator.ql create mode 100644 java/ql/src/Language Abuse/WrappedIteratorBad1.java create mode 100644 java/ql/src/Language Abuse/WrappedIteratorBad2.java create mode 100644 java/ql/src/Language Abuse/WrappedIteratorGood.java create mode 100644 java/ql/src/Likely Bugs/Arithmetic/BadAbsOfRandom.java create mode 100644 java/ql/src/Likely Bugs/Arithmetic/BadAbsOfRandom.qhelp create mode 100644 java/ql/src/Likely Bugs/Arithmetic/BadAbsOfRandom.ql create mode 100644 java/ql/src/Likely Bugs/Arithmetic/BadCheckOdd.java create mode 100644 java/ql/src/Likely Bugs/Arithmetic/BadCheckOdd.qhelp create mode 100644 java/ql/src/Likely Bugs/Arithmetic/BadCheckOdd.ql create mode 100644 java/ql/src/Likely Bugs/Arithmetic/BadCheckOddGood.java create mode 100644 java/ql/src/Likely Bugs/Arithmetic/CondExprTypes.java create mode 100644 java/ql/src/Likely Bugs/Arithmetic/CondExprTypes.qhelp create mode 100644 java/ql/src/Likely Bugs/Arithmetic/CondExprTypes.ql create mode 100644 java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.java create mode 100644 java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.qhelp create mode 100644 java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.ql create mode 100644 java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstantGood.java create mode 100644 java/ql/src/Likely Bugs/Arithmetic/InformationLoss.qhelp create mode 100644 java/ql/src/Likely Bugs/Arithmetic/InformationLoss.ql create mode 100644 java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.java create mode 100644 java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.qhelp create mode 100644 java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql create mode 100644 java/ql/src/Likely Bugs/Arithmetic/IntMultToLongGood.java create mode 100644 java/ql/src/Likely Bugs/Arithmetic/MultiplyRemainder.qhelp create mode 100644 java/ql/src/Likely Bugs/Arithmetic/MultiplyRemainder.ql create mode 100644 java/ql/src/Likely Bugs/Arithmetic/OctalLiteral.java create mode 100644 java/ql/src/Likely Bugs/Arithmetic/OctalLiteral.qhelp create mode 100644 java/ql/src/Likely Bugs/Arithmetic/OctalLiteral.ql create mode 100644 java/ql/src/Likely Bugs/Arithmetic/RandomUsedOnce.java create mode 100644 java/ql/src/Likely Bugs/Arithmetic/RandomUsedOnce.qhelp create mode 100644 java/ql/src/Likely Bugs/Arithmetic/RandomUsedOnce.ql create mode 100644 java/ql/src/Likely Bugs/Arithmetic/WhitespaceContradictsPrecedence.qhelp create mode 100644 java/ql/src/Likely Bugs/Arithmetic/WhitespaceContradictsPrecedence.ql create mode 100644 java/ql/src/Likely Bugs/Cloning/MissingCallToSuperClone.qhelp create mode 100644 java/ql/src/Likely Bugs/Cloning/MissingCallToSuperClone.ql create mode 100644 java/ql/src/Likely Bugs/Cloning/MissingCallToSuperCloneBad.java create mode 100644 java/ql/src/Likely Bugs/Cloning/MissingCallToSuperCloneGood.java create mode 100644 java/ql/src/Likely Bugs/Cloning/MissingCloneDetails.qhelp create mode 100644 java/ql/src/Likely Bugs/Cloning/MissingMethodClone.qhelp create mode 100644 java/ql/src/Likely Bugs/Cloning/MissingMethodClone.ql create mode 100644 java/ql/src/Likely Bugs/Cloning/MissingMethodCloneBad.java create mode 100644 java/ql/src/Likely Bugs/Cloning/MissingMethodCloneGood.java create mode 100644 java/ql/src/Likely Bugs/Collections/ArrayIndexOutOfBounds.qhelp create mode 100644 java/ql/src/Likely Bugs/Collections/ArrayIndexOutOfBounds.ql create mode 100644 java/ql/src/Likely Bugs/Collections/ArrayIndexOutOfBoundsBad.java create mode 100644 java/ql/src/Likely Bugs/Collections/ArrayIndexOutOfBoundsGood.java create mode 100644 java/ql/src/Likely Bugs/Collections/Containers.qll create mode 100644 java/ql/src/Likely Bugs/Collections/ContainsTypeMismatch.java create mode 100644 java/ql/src/Likely Bugs/Collections/ContainsTypeMismatch.qhelp create mode 100644 java/ql/src/Likely Bugs/Collections/ContainsTypeMismatch.ql create mode 100644 java/ql/src/Likely Bugs/Collections/ContainsTypeMismatch2.java create mode 100644 java/ql/src/Likely Bugs/Collections/IteratorRemoveMayFail.java create mode 100644 java/ql/src/Likely Bugs/Collections/IteratorRemoveMayFail.qhelp create mode 100644 java/ql/src/Likely Bugs/Collections/IteratorRemoveMayFail.ql create mode 100644 java/ql/src/Likely Bugs/Collections/IteratorRemoveMayFailGood.java create mode 100644 java/ql/src/Likely Bugs/Collections/ReadOnlyContainer.java create mode 100644 java/ql/src/Likely Bugs/Collections/ReadOnlyContainer.qhelp create mode 100644 java/ql/src/Likely Bugs/Collections/ReadOnlyContainer.ql create mode 100644 java/ql/src/Likely Bugs/Collections/RemoveTypeMismatch.java create mode 100644 java/ql/src/Likely Bugs/Collections/RemoveTypeMismatch.qhelp create mode 100644 java/ql/src/Likely Bugs/Collections/RemoveTypeMismatch.ql create mode 100644 java/ql/src/Likely Bugs/Collections/RemoveTypeMismatch2.java create mode 100644 java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.java create mode 100644 java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.qhelp create mode 100644 java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.ql create mode 100644 java/ql/src/Likely Bugs/Collections/WriteOnlyContainerGood.java create mode 100644 java/ql/src/Likely Bugs/Comparison/BitwiseSignCheck.java create mode 100644 java/ql/src/Likely Bugs/Comparison/BitwiseSignCheck.qhelp create mode 100644 java/ql/src/Likely Bugs/Comparison/BitwiseSignCheck.ql create mode 100644 java/ql/src/Likely Bugs/Comparison/BitwiseSignCheckGood.java create mode 100644 java/ql/src/Likely Bugs/Comparison/CompareIdenticalValues.java create mode 100644 java/ql/src/Likely Bugs/Comparison/CompareIdenticalValues.qhelp create mode 100644 java/ql/src/Likely Bugs/Comparison/CompareIdenticalValues.ql create mode 100644 java/ql/src/Likely Bugs/Comparison/CovariantCompareTo.java create mode 100644 java/ql/src/Likely Bugs/Comparison/CovariantCompareTo.qhelp create mode 100644 java/ql/src/Likely Bugs/Comparison/CovariantCompareTo.ql create mode 100644 java/ql/src/Likely Bugs/Comparison/CovariantEquals.java create mode 100644 java/ql/src/Likely Bugs/Comparison/CovariantEquals.qhelp create mode 100644 java/ql/src/Likely Bugs/Comparison/CovariantEquals.ql create mode 100644 java/ql/src/Likely Bugs/Comparison/DefineEqualsWhenAddingFields.java create mode 100644 java/ql/src/Likely Bugs/Comparison/DefineEqualsWhenAddingFields.qhelp create mode 100644 java/ql/src/Likely Bugs/Comparison/DefineEqualsWhenAddingFields.ql create mode 100644 java/ql/src/Likely Bugs/Comparison/Equality.qll create mode 100644 java/ql/src/Likely Bugs/Comparison/EqualsArray.java create mode 100644 java/ql/src/Likely Bugs/Comparison/EqualsArray.qhelp create mode 100644 java/ql/src/Likely Bugs/Comparison/EqualsArray.ql create mode 100644 java/ql/src/Likely Bugs/Comparison/EqualsUsesInstanceOf.java create mode 100644 java/ql/src/Likely Bugs/Comparison/EqualsUsesInstanceOf.qhelp create mode 100644 java/ql/src/Likely Bugs/Comparison/EqualsUsesInstanceOf.ql create mode 100644 java/ql/src/Likely Bugs/Comparison/HashedButNoHash.java create mode 100644 java/ql/src/Likely Bugs/Comparison/HashedButNoHash.qhelp create mode 100644 java/ql/src/Likely Bugs/Comparison/HashedButNoHash.ql create mode 100644 java/ql/src/Likely Bugs/Comparison/HashedButNoHashBad.java create mode 100644 java/ql/src/Likely Bugs/Comparison/IncomparableEquals.java create mode 100644 java/ql/src/Likely Bugs/Comparison/IncomparableEquals.qhelp create mode 100644 java/ql/src/Likely Bugs/Comparison/IncomparableEquals.ql create mode 100644 java/ql/src/Likely Bugs/Comparison/InconsistentCompareTo.java create mode 100644 java/ql/src/Likely Bugs/Comparison/InconsistentCompareTo.qhelp create mode 100644 java/ql/src/Likely Bugs/Comparison/InconsistentCompareTo.ql create mode 100644 java/ql/src/Likely Bugs/Comparison/InconsistentCompareToGood.java create mode 100644 java/ql/src/Likely Bugs/Comparison/InconsistentEqualsHashCode.java create mode 100644 java/ql/src/Likely Bugs/Comparison/InconsistentEqualsHashCode.qhelp create mode 100644 java/ql/src/Likely Bugs/Comparison/InconsistentEqualsHashCode.ql create mode 100644 java/ql/src/Likely Bugs/Comparison/InconsistentEqualsHashCodeGood.java create mode 100644 java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.java create mode 100644 java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.qhelp create mode 100644 java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.ql create mode 100644 java/ql/src/Likely Bugs/Comparison/NoAssignInBooleanExprs.java create mode 100644 java/ql/src/Likely Bugs/Comparison/NoAssignInBooleanExprs.qhelp create mode 100644 java/ql/src/Likely Bugs/Comparison/NoAssignInBooleanExprs.ql create mode 100644 java/ql/src/Likely Bugs/Comparison/NoComparisonOnFloats.java create mode 100644 java/ql/src/Likely Bugs/Comparison/NoComparisonOnFloats.qhelp create mode 100644 java/ql/src/Likely Bugs/Comparison/NoComparisonOnFloats.ql create mode 100644 java/ql/src/Likely Bugs/Comparison/NoComparisonOnFloatsGood.java create mode 100644 java/ql/src/Likely Bugs/Comparison/ObjectComparison.qhelp create mode 100644 java/ql/src/Likely Bugs/Comparison/ObjectComparison.ql create mode 100644 java/ql/src/Likely Bugs/Comparison/RefEqBoxed.java create mode 100644 java/ql/src/Likely Bugs/Comparison/RefEqBoxed.qhelp create mode 100644 java/ql/src/Likely Bugs/Comparison/RefEqBoxed.ql create mode 100644 java/ql/src/Likely Bugs/Comparison/RefEqBoxedBad.java create mode 100644 java/ql/src/Likely Bugs/Comparison/StringComparison.java create mode 100644 java/ql/src/Likely Bugs/Comparison/StringComparison.qhelp create mode 100644 java/ql/src/Likely Bugs/Comparison/StringComparison.ql create mode 100644 java/ql/src/Likely Bugs/Comparison/StringComparisonGood.java create mode 100644 java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.java create mode 100644 java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.qhelp create mode 100644 java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.ql create mode 100644 java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.qll create mode 100644 java/ql/src/Likely Bugs/Comparison/WrongNanComparison.qhelp create mode 100644 java/ql/src/Likely Bugs/Comparison/WrongNanComparison.ql create mode 100644 java/ql/src/Likely Bugs/Concurrency/BusyWait.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/BusyWait.qhelp create mode 100644 java/ql/src/Likely Bugs/Concurrency/BusyWait.ql create mode 100644 java/ql/src/Likely Bugs/Concurrency/BusyWaitGood.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/CallsToConditionWait.qhelp create mode 100644 java/ql/src/Likely Bugs/Concurrency/CallsToConditionWait.ql create mode 100644 java/ql/src/Likely Bugs/Concurrency/CallsToRunnableRun.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/CallsToRunnableRun.qhelp create mode 100644 java/ql/src/Likely Bugs/Concurrency/CallsToRunnableRun.ql create mode 100644 java/ql/src/Likely Bugs/Concurrency/CallsToRunnableRunFixed.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/DateFormatThreadUnsafe.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/DateFormatThreadUnsafe.qhelp create mode 100644 java/ql/src/Likely Bugs/Concurrency/DateFormatThreadUnsafe.ql create mode 100644 java/ql/src/Likely Bugs/Concurrency/DateFormatThreadUnsafeGood.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/EmptyRunMethodInThread.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/EmptyRunMethodInThread.qhelp create mode 100644 java/ql/src/Likely Bugs/Concurrency/EmptyRunMethodInThread.ql create mode 100644 java/ql/src/Likely Bugs/Concurrency/EmptyRunMethodInThreadGood.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/FutileSynchOnField.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/FutileSynchOnField.qhelp create mode 100644 java/ql/src/Likely Bugs/Concurrency/FutileSynchOnField.ql create mode 100644 java/ql/src/Likely Bugs/Concurrency/FutileSynchOnFieldGood.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/InconsistentAccess.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/InconsistentAccess.qhelp create mode 100644 java/ql/src/Likely Bugs/Concurrency/InconsistentAccess.ql create mode 100644 java/ql/src/Likely Bugs/Concurrency/LazyInitStaticField.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/LazyInitStaticField.qhelp create mode 100644 java/ql/src/Likely Bugs/Concurrency/LazyInitStaticField.ql create mode 100644 java/ql/src/Likely Bugs/Concurrency/LazyInitStaticFieldGood.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/NonSynchronizedOverride.qhelp create mode 100644 java/ql/src/Likely Bugs/Concurrency/NonSynchronizedOverride.ql create mode 100644 java/ql/src/Likely Bugs/Concurrency/NotifyNotNotifyAll.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/NotifyNotNotifyAll.qhelp create mode 100644 java/ql/src/Likely Bugs/Concurrency/NotifyNotNotifyAll.ql create mode 100644 java/ql/src/Likely Bugs/Concurrency/NotifyWithoutSynch.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/NotifyWithoutSynch.qhelp create mode 100644 java/ql/src/Likely Bugs/Concurrency/NotifyWithoutSynch.ql create mode 100644 java/ql/src/Likely Bugs/Concurrency/PriorityCalls.qhelp create mode 100644 java/ql/src/Likely Bugs/Concurrency/PriorityCalls.ql create mode 100644 java/ql/src/Likely Bugs/Concurrency/SleepWithLock.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/SleepWithLock.qhelp create mode 100644 java/ql/src/Likely Bugs/Concurrency/SleepWithLock.ql create mode 100644 java/ql/src/Likely Bugs/Concurrency/StartInConstructor.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/StartInConstructor.qhelp create mode 100644 java/ql/src/Likely Bugs/Concurrency/StartInConstructor.ql create mode 100644 java/ql/src/Likely Bugs/Concurrency/StartInConstructorGood.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/SynchOnBoxedType.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/SynchOnBoxedType.qhelp create mode 100644 java/ql/src/Likely Bugs/Concurrency/SynchOnBoxedType.ql create mode 100644 java/ql/src/Likely Bugs/Concurrency/SynchOnBoxedTypeGood.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.qhelp create mode 100644 java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.ql create mode 100644 java/ql/src/Likely Bugs/Concurrency/SynchWriteObject.qhelp create mode 100644 java/ql/src/Likely Bugs/Concurrency/SynchWriteObject.ql create mode 100644 java/ql/src/Likely Bugs/Concurrency/UnreleasedLock.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/UnreleasedLock.qhelp create mode 100644 java/ql/src/Likely Bugs/Concurrency/UnreleasedLock.ql create mode 100644 java/ql/src/Likely Bugs/Concurrency/WaitOutsideLoop.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/WaitOutsideLoop.qhelp create mode 100644 java/ql/src/Likely Bugs/Concurrency/WaitOutsideLoop.ql create mode 100644 java/ql/src/Likely Bugs/Concurrency/WaitWithTwoLocks.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/WaitWithTwoLocks.qhelp create mode 100644 java/ql/src/Likely Bugs/Concurrency/WaitWithTwoLocks.ql create mode 100644 java/ql/src/Likely Bugs/Concurrency/WaitWithTwoLocksGood.java create mode 100644 java/ql/src/Likely Bugs/Concurrency/YieldCalls.qhelp create mode 100644 java/ql/src/Likely Bugs/Concurrency/YieldCalls.ql create mode 100644 java/ql/src/Likely Bugs/Finalization/NullifiedSuperFinalize.java create mode 100644 java/ql/src/Likely Bugs/Finalization/NullifiedSuperFinalize.qhelp create mode 100644 java/ql/src/Likely Bugs/Finalization/NullifiedSuperFinalize.ql create mode 100644 java/ql/src/Likely Bugs/Finalization/NullifiedSuperFinalizeGuarded.java create mode 100644 java/ql/src/Likely Bugs/Frameworks/JUnit/BadSuiteMethod.java create mode 100644 java/ql/src/Likely Bugs/Frameworks/JUnit/BadSuiteMethod.qhelp create mode 100644 java/ql/src/Likely Bugs/Frameworks/JUnit/BadSuiteMethod.ql create mode 100644 java/ql/src/Likely Bugs/Frameworks/JUnit/TearDownNoSuper.java create mode 100644 java/ql/src/Likely Bugs/Frameworks/JUnit/TearDownNoSuper.qhelp create mode 100644 java/ql/src/Likely Bugs/Frameworks/JUnit/TearDownNoSuper.ql create mode 100644 java/ql/src/Likely Bugs/Frameworks/JUnit/TestCaseNoTests.java create mode 100644 java/ql/src/Likely Bugs/Frameworks/JUnit/TestCaseNoTests.qhelp create mode 100644 java/ql/src/Likely Bugs/Frameworks/JUnit/TestCaseNoTests.ql create mode 100644 java/ql/src/Likely Bugs/Frameworks/Swing/BadlyOverriddenAdapter.java create mode 100644 java/ql/src/Likely Bugs/Frameworks/Swing/BadlyOverriddenAdapter.qhelp create mode 100644 java/ql/src/Likely Bugs/Frameworks/Swing/BadlyOverriddenAdapter.ql create mode 100644 java/ql/src/Likely Bugs/Frameworks/Swing/BadlyOverriddenAdapterGood.java create mode 100644 java/ql/src/Likely Bugs/Frameworks/Swing/ThreadSafety.java create mode 100644 java/ql/src/Likely Bugs/Frameworks/Swing/ThreadSafety.qhelp create mode 100644 java/ql/src/Likely Bugs/Frameworks/Swing/ThreadSafety.ql create mode 100644 java/ql/src/Likely Bugs/Frameworks/Swing/ThreadSafetyGood.java create mode 100644 java/ql/src/Likely Bugs/I18N/MissingLocaleArgument.java create mode 100644 java/ql/src/Likely Bugs/I18N/MissingLocaleArgument.qhelp create mode 100644 java/ql/src/Likely Bugs/I18N/MissingLocaleArgument.ql create mode 100644 java/ql/src/Likely Bugs/Inheritance/NoNonFinalInConstructor.java create mode 100644 java/ql/src/Likely Bugs/Inheritance/NoNonFinalInConstructor.qhelp create mode 100644 java/ql/src/Likely Bugs/Inheritance/NoNonFinalInConstructor.ql create mode 100644 java/ql/src/Likely Bugs/Likely Typos/ConstructorTypo.java create mode 100644 java/ql/src/Likely Bugs/Likely Typos/ConstructorTypo.qhelp create mode 100644 java/ql/src/Likely Bugs/Likely Typos/ConstructorTypo.ql create mode 100644 java/ql/src/Likely Bugs/Likely Typos/ContainerSizeCmpZero.java create mode 100644 java/ql/src/Likely Bugs/Likely Typos/ContainerSizeCmpZero.qhelp create mode 100644 java/ql/src/Likely Bugs/Likely Typos/ContainerSizeCmpZero.ql create mode 100644 java/ql/src/Likely Bugs/Likely Typos/ContainerSizeCmpZeroGood.java create mode 100644 java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.java create mode 100644 java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.qhelp create mode 100644 java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.ql create mode 100644 java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecksGood.java create mode 100644 java/ql/src/Likely Bugs/Likely Typos/DangerousNonCircuitLogic.java create mode 100644 java/ql/src/Likely Bugs/Likely Typos/DangerousNonCircuitLogic.qhelp create mode 100644 java/ql/src/Likely Bugs/Likely Typos/DangerousNonCircuitLogic.ql create mode 100644 java/ql/src/Likely Bugs/Likely Typos/EqualsTypo.java create mode 100644 java/ql/src/Likely Bugs/Likely Typos/EqualsTypo.qhelp create mode 100644 java/ql/src/Likely Bugs/Likely Typos/EqualsTypo.ql create mode 100644 java/ql/src/Likely Bugs/Likely Typos/FormatStringsOverview.qhelp create mode 100644 java/ql/src/Likely Bugs/Likely Typos/FormatStringsRefs.qhelp create mode 100644 java/ql/src/Likely Bugs/Likely Typos/HashCodeTypo.java create mode 100644 java/ql/src/Likely Bugs/Likely Typos/HashCodeTypo.qhelp create mode 100644 java/ql/src/Likely Bugs/Likely Typos/HashCodeTypo.ql create mode 100644 java/ql/src/Likely Bugs/Likely Typos/MissingFormatArg.qhelp create mode 100644 java/ql/src/Likely Bugs/Likely Typos/MissingFormatArg.ql create mode 100644 java/ql/src/Likely Bugs/Likely Typos/MissingFormatArgBad.java create mode 100644 java/ql/src/Likely Bugs/Likely Typos/MissingSpaceTypo.qhelp create mode 100644 java/ql/src/Likely Bugs/Likely Typos/MissingSpaceTypo.ql create mode 100644 java/ql/src/Likely Bugs/Likely Typos/MissingSpaceTypoBad.java create mode 100644 java/ql/src/Likely Bugs/Likely Typos/NestedLoopsSameVariable.qhelp create mode 100644 java/ql/src/Likely Bugs/Likely Typos/NestedLoopsSameVariable.ql create mode 100644 java/ql/src/Likely Bugs/Likely Typos/SelfAssignment.java create mode 100644 java/ql/src/Likely Bugs/Likely Typos/SelfAssignment.qhelp create mode 100644 java/ql/src/Likely Bugs/Likely Typos/SelfAssignment.ql create mode 100644 java/ql/src/Likely Bugs/Likely Typos/StringBufferCharInit.java create mode 100644 java/ql/src/Likely Bugs/Likely Typos/StringBufferCharInit.qhelp create mode 100644 java/ql/src/Likely Bugs/Likely Typos/StringBufferCharInit.ql create mode 100644 java/ql/src/Likely Bugs/Likely Typos/ToStringTypo.java create mode 100644 java/ql/src/Likely Bugs/Likely Typos/ToStringTypo.qhelp create mode 100644 java/ql/src/Likely Bugs/Likely Typos/ToStringTypo.ql create mode 100644 java/ql/src/Likely Bugs/Likely Typos/UnusedFormatArg.qhelp create mode 100644 java/ql/src/Likely Bugs/Likely Typos/UnusedFormatArg.ql create mode 100644 java/ql/src/Likely Bugs/Likely Typos/UnusedFormatArgBad.java create mode 100644 java/ql/src/Likely Bugs/Nullness/NullAlways.java create mode 100644 java/ql/src/Likely Bugs/Nullness/NullAlways.qhelp create mode 100644 java/ql/src/Likely Bugs/Nullness/NullAlways.ql create mode 100644 java/ql/src/Likely Bugs/Nullness/NullExprDeref.java create mode 100644 java/ql/src/Likely Bugs/Nullness/NullExprDeref.qhelp create mode 100644 java/ql/src/Likely Bugs/Nullness/NullExprDeref.ql create mode 100644 java/ql/src/Likely Bugs/Nullness/NullMaybe.java create mode 100644 java/ql/src/Likely Bugs/Nullness/NullMaybe.qhelp create mode 100644 java/ql/src/Likely Bugs/Nullness/NullMaybe.ql create mode 100644 java/ql/src/Likely Bugs/Reflection/AnnotationPresentCheck.java create mode 100644 java/ql/src/Likely Bugs/Reflection/AnnotationPresentCheck.qhelp create mode 100644 java/ql/src/Likely Bugs/Reflection/AnnotationPresentCheck.ql create mode 100644 java/ql/src/Likely Bugs/Reflection/AnnotationPresentCheckFix.java create mode 100644 java/ql/src/Likely Bugs/Resource Leaks/CloseReader.java create mode 100644 java/ql/src/Likely Bugs/Resource Leaks/CloseReader.qhelp create mode 100644 java/ql/src/Likely Bugs/Resource Leaks/CloseReader.ql create mode 100644 java/ql/src/Likely Bugs/Resource Leaks/CloseReaderGood.java create mode 100644 java/ql/src/Likely Bugs/Resource Leaks/CloseReaderNested.java create mode 100644 java/ql/src/Likely Bugs/Resource Leaks/CloseReaderNestedGood.java create mode 100644 java/ql/src/Likely Bugs/Resource Leaks/CloseSql.java create mode 100644 java/ql/src/Likely Bugs/Resource Leaks/CloseSql.qhelp create mode 100644 java/ql/src/Likely Bugs/Resource Leaks/CloseSql.ql create mode 100644 java/ql/src/Likely Bugs/Resource Leaks/CloseSqlGood.java create mode 100644 java/ql/src/Likely Bugs/Resource Leaks/CloseType.qll create mode 100644 java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.java create mode 100644 java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.qhelp create mode 100644 java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.ql create mode 100644 java/ql/src/Likely Bugs/Resource Leaks/CloseWriterGood.java create mode 100644 java/ql/src/Likely Bugs/Resource Leaks/CloseWriterNested.java create mode 100644 java/ql/src/Likely Bugs/Resource Leaks/CloseWriterNestedGood.java create mode 100644 java/ql/src/Likely Bugs/Serialization/IncorrectSerialVersionUID.java create mode 100644 java/ql/src/Likely Bugs/Serialization/IncorrectSerialVersionUID.qhelp create mode 100644 java/ql/src/Likely Bugs/Serialization/IncorrectSerialVersionUID.ql create mode 100644 java/ql/src/Likely Bugs/Serialization/IncorrectSerializableMethods.java create mode 100644 java/ql/src/Likely Bugs/Serialization/IncorrectSerializableMethods.qhelp create mode 100644 java/ql/src/Likely Bugs/Serialization/IncorrectSerializableMethods.ql create mode 100644 java/ql/src/Likely Bugs/Serialization/IncorrectSerializableMethodsSig.java create mode 100644 java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorOnExternalizable.java create mode 100644 java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorOnExternalizable.qhelp create mode 100644 java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorOnExternalizable.ql create mode 100644 java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorsOnSerializable.java create mode 100644 java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorsOnSerializable.qhelp create mode 100644 java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorsOnSerializable.ql create mode 100644 java/ql/src/Likely Bugs/Serialization/NonSerializableComparator.java create mode 100644 java/ql/src/Likely Bugs/Serialization/NonSerializableComparator.qhelp create mode 100644 java/ql/src/Likely Bugs/Serialization/NonSerializableComparator.ql create mode 100644 java/ql/src/Likely Bugs/Serialization/NonSerializableField.java create mode 100644 java/ql/src/Likely Bugs/Serialization/NonSerializableField.qhelp create mode 100644 java/ql/src/Likely Bugs/Serialization/NonSerializableField.ql create mode 100644 java/ql/src/Likely Bugs/Serialization/NonSerializableFieldTooGeneral.java create mode 100644 java/ql/src/Likely Bugs/Serialization/NonSerializableInnerClass.java create mode 100644 java/ql/src/Likely Bugs/Serialization/NonSerializableInnerClass.qhelp create mode 100644 java/ql/src/Likely Bugs/Serialization/NonSerializableInnerClass.ql create mode 100644 java/ql/src/Likely Bugs/Serialization/ReadResolveObject.java create mode 100644 java/ql/src/Likely Bugs/Serialization/ReadResolveObject.qhelp create mode 100644 java/ql/src/Likely Bugs/Serialization/ReadResolveObject.ql create mode 100644 java/ql/src/Likely Bugs/Serialization/TransientNotSerializable.java create mode 100644 java/ql/src/Likely Bugs/Serialization/TransientNotSerializable.qhelp create mode 100644 java/ql/src/Likely Bugs/Serialization/TransientNotSerializable.ql create mode 100644 java/ql/src/Likely Bugs/Statements/Chaining.qll create mode 100644 java/ql/src/Likely Bugs/Statements/EmptyBlock.java create mode 100644 java/ql/src/Likely Bugs/Statements/EmptyBlock.qhelp create mode 100644 java/ql/src/Likely Bugs/Statements/EmptyBlock.ql create mode 100644 java/ql/src/Likely Bugs/Statements/EmptySynchronizedBlock.qhelp create mode 100644 java/ql/src/Likely Bugs/Statements/EmptySynchronizedBlock.ql create mode 100644 java/ql/src/Likely Bugs/Statements/ImpossibleCast.qhelp create mode 100644 java/ql/src/Likely Bugs/Statements/ImpossibleCast.ql create mode 100644 java/ql/src/Likely Bugs/Statements/InconsistentCallOnResult.java create mode 100644 java/ql/src/Likely Bugs/Statements/InconsistentCallOnResult.qhelp create mode 100644 java/ql/src/Likely Bugs/Statements/InconsistentCallOnResult.ql create mode 100644 java/ql/src/Likely Bugs/Statements/MissingEnumInSwitch.java create mode 100644 java/ql/src/Likely Bugs/Statements/MissingEnumInSwitch.qhelp create mode 100644 java/ql/src/Likely Bugs/Statements/MissingEnumInSwitch.ql create mode 100644 java/ql/src/Likely Bugs/Statements/PartiallyMaskedCatch.java create mode 100644 java/ql/src/Likely Bugs/Statements/PartiallyMaskedCatch.qhelp create mode 100644 java/ql/src/Likely Bugs/Statements/PartiallyMaskedCatch.ql create mode 100644 java/ql/src/Likely Bugs/Statements/ReturnValueIgnored.java create mode 100644 java/ql/src/Likely Bugs/Statements/ReturnValueIgnored.qhelp create mode 100644 java/ql/src/Likely Bugs/Statements/ReturnValueIgnored.ql create mode 100644 java/ql/src/Likely Bugs/Statements/StaticFieldWrittenByInstance.java create mode 100644 java/ql/src/Likely Bugs/Statements/StaticFieldWrittenByInstance.qhelp create mode 100644 java/ql/src/Likely Bugs/Statements/StaticFieldWrittenByInstance.ql create mode 100644 java/ql/src/Likely Bugs/Statements/UseBraces.java create mode 100644 java/ql/src/Likely Bugs/Statements/UseBraces.qhelp create mode 100644 java/ql/src/Likely Bugs/Statements/UseBraces.ql create mode 100644 java/ql/src/Likely Bugs/Statements/UseBraces2.java create mode 100644 java/ql/src/Likely Bugs/Statements/UseBracesGood.java create mode 100644 java/ql/src/Likely Bugs/Termination/ConstantLoopCondition.qhelp create mode 100644 java/ql/src/Likely Bugs/Termination/ConstantLoopCondition.ql create mode 100644 java/ql/src/Likely Bugs/Termination/ConstantLoopConditionBad.java create mode 100644 java/ql/src/Likely Bugs/Termination/ConstantLoopConditionGood.java create mode 100644 java/ql/src/Likely Bugs/Termination/SpinOnField.java create mode 100644 java/ql/src/Likely Bugs/Termination/SpinOnField.qhelp create mode 100644 java/ql/src/Likely Bugs/Termination/SpinOnField.ql create mode 100644 java/ql/src/META-INF/MANIFEST.MF create mode 100644 java/ql/src/Metrics/Authors/AuthorsPerFile.qhelp create mode 100644 java/ql/src/Metrics/Authors/AuthorsPerFile.ql create mode 100644 java/ql/src/Metrics/Callables/CCyclomaticComplexity.java create mode 100644 java/ql/src/Metrics/Callables/CCyclomaticComplexity.qhelp create mode 100644 java/ql/src/Metrics/Callables/CCyclomaticComplexity.ql create mode 100644 java/ql/src/Metrics/Callables/CCyclomaticComplexity_ControlFlow.gv create mode 100644 java/ql/src/Metrics/Callables/CCyclomaticComplexity_ControlFlow.png create mode 100644 java/ql/src/Metrics/Callables/CLinesOfCode.qhelp create mode 100644 java/ql/src/Metrics/Callables/CLinesOfCode.ql create mode 100644 java/ql/src/Metrics/Callables/CLinesOfComment.qhelp create mode 100644 java/ql/src/Metrics/Callables/CLinesOfComment.ql create mode 100644 java/ql/src/Metrics/Callables/CNumberOfCalls.qhelp create mode 100644 java/ql/src/Metrics/Callables/CNumberOfCalls.ql create mode 100644 java/ql/src/Metrics/Callables/CNumberOfParameters.qhelp create mode 100644 java/ql/src/Metrics/Callables/CNumberOfParameters.ql create mode 100644 java/ql/src/Metrics/Callables/CNumberOfParameters1.java create mode 100644 java/ql/src/Metrics/Callables/CNumberOfParameters1Good.java create mode 100644 java/ql/src/Metrics/Callables/CNumberOfParameters2.java create mode 100644 java/ql/src/Metrics/Callables/CNumberOfParameters2Good.java create mode 100644 java/ql/src/Metrics/Callables/CNumberOfStatements.qhelp create mode 100644 java/ql/src/Metrics/Callables/CNumberOfStatements.ql create mode 100644 java/ql/src/Metrics/Callables/StatementNestingDepth.java create mode 100644 java/ql/src/Metrics/Callables/StatementNestingDepth.qhelp create mode 100644 java/ql/src/Metrics/Callables/StatementNestingDepth.ql create mode 100644 java/ql/src/Metrics/Callables/StatementNestingDepthGood.java create mode 100644 java/ql/src/Metrics/Dependencies/ExternalDependencies.ql create mode 100644 java/ql/src/Metrics/Dependencies/ExternalDependenciesSourceLinks.ql create mode 100644 java/ql/src/Metrics/Files/DuplicationProblems.qhelp create mode 100644 java/ql/src/Metrics/Files/FAfferentCoupling.qhelp create mode 100644 java/ql/src/Metrics/Files/FAfferentCoupling.ql create mode 100644 java/ql/src/Metrics/Files/FCommentRatio.qhelp create mode 100644 java/ql/src/Metrics/Files/FCommentRatio.ql create mode 100644 java/ql/src/Metrics/Files/FCyclomaticComplexity.java create mode 100644 java/ql/src/Metrics/Files/FCyclomaticComplexity.qhelp create mode 100644 java/ql/src/Metrics/Files/FCyclomaticComplexity.ql create mode 100644 java/ql/src/Metrics/Files/FCyclomaticComplexity_ControlFlow.gv create mode 100644 java/ql/src/Metrics/Files/FCyclomaticComplexity_ControlFlow.png create mode 100644 java/ql/src/Metrics/Files/FEfferentCoupling.qhelp create mode 100644 java/ql/src/Metrics/Files/FEfferentCoupling.ql create mode 100644 java/ql/src/Metrics/Files/FEfferentCoupling_SplitAfter.gv create mode 100644 java/ql/src/Metrics/Files/FEfferentCoupling_SplitAfter.png create mode 100644 java/ql/src/Metrics/Files/FEfferentCoupling_SplitBefore.gv create mode 100644 java/ql/src/Metrics/Files/FEfferentCoupling_SplitBefore.png create mode 100644 java/ql/src/Metrics/Files/FLines.ql create mode 100644 java/ql/src/Metrics/Files/FLinesOfCode.qhelp create mode 100644 java/ql/src/Metrics/Files/FLinesOfCode.ql create mode 100644 java/ql/src/Metrics/Files/FLinesOfComment.qhelp create mode 100644 java/ql/src/Metrics/Files/FLinesOfComment.ql create mode 100644 java/ql/src/Metrics/Files/FLinesOfCommentedCode.qhelp create mode 100644 java/ql/src/Metrics/Files/FLinesOfCommentedCode.ql create mode 100644 java/ql/src/Metrics/Files/FLinesOfDuplicatedCode.qhelp create mode 100644 java/ql/src/Metrics/Files/FLinesOfDuplicatedCode.ql create mode 100644 java/ql/src/Metrics/Files/FLinesOfSimilarCode.qhelp create mode 100644 java/ql/src/Metrics/Files/FLinesOfSimilarCode.ql create mode 100644 java/ql/src/Metrics/Files/FNumberOfClasses.qhelp create mode 100644 java/ql/src/Metrics/Files/FNumberOfClasses.ql create mode 100644 java/ql/src/Metrics/Files/FNumberOfInterfaces.qhelp create mode 100644 java/ql/src/Metrics/Files/FNumberOfInterfaces.ql create mode 100644 java/ql/src/Metrics/Files/FNumberOfTests.qhelp create mode 100644 java/ql/src/Metrics/Files/FNumberOfTests.ql create mode 100644 java/ql/src/Metrics/Files/FSelfContainedness.qhelp create mode 100644 java/ql/src/Metrics/Files/FSelfContainedness.ql create mode 100644 java/ql/src/Metrics/History/HChurn.qhelp create mode 100644 java/ql/src/Metrics/History/HChurn.ql create mode 100644 java/ql/src/Metrics/History/HLinesAdded.qhelp create mode 100644 java/ql/src/Metrics/History/HLinesAdded.ql create mode 100644 java/ql/src/Metrics/History/HLinesDeleted.qhelp create mode 100644 java/ql/src/Metrics/History/HLinesDeleted.ql create mode 100644 java/ql/src/Metrics/History/HNumberOfAuthors.qhelp create mode 100644 java/ql/src/Metrics/History/HNumberOfAuthors.ql create mode 100644 java/ql/src/Metrics/History/HNumberOfChanges.qhelp create mode 100644 java/ql/src/Metrics/History/HNumberOfChanges.ql create mode 100644 java/ql/src/Metrics/History/HNumberOfRecentChanges.qhelp create mode 100644 java/ql/src/Metrics/History/HNumberOfRecentChanges.ql create mode 100644 java/ql/src/Metrics/Internal/CallableDisplayStrings.ql create mode 100644 java/ql/src/Metrics/Internal/CallableExtents.ql create mode 100644 java/ql/src/Metrics/Internal/CallableSourceLinks.ql create mode 100644 java/ql/src/Metrics/Internal/Extents.qll create mode 100644 java/ql/src/Metrics/Internal/ReftypeDisplayStrings.ql create mode 100644 java/ql/src/Metrics/Internal/ReftypeExtents.ql create mode 100644 java/ql/src/Metrics/Internal/ReftypeSourceLinks.ql create mode 100644 java/ql/src/Metrics/RefTypes/TAfferentCoupling.qhelp create mode 100644 java/ql/src/Metrics/RefTypes/TAfferentCoupling.ql create mode 100644 java/ql/src/Metrics/RefTypes/TEfferentCoupling.java create mode 100644 java/ql/src/Metrics/RefTypes/TEfferentCoupling.qhelp create mode 100644 java/ql/src/Metrics/RefTypes/TEfferentCoupling.ql create mode 100644 java/ql/src/Metrics/RefTypes/TEfferentCouplingGood.java create mode 100644 java/ql/src/Metrics/RefTypes/TEfferentSourceCoupling.qhelp create mode 100644 java/ql/src/Metrics/RefTypes/TEfferentSourceCoupling.ql create mode 100644 java/ql/src/Metrics/RefTypes/TInheritanceDepth.qhelp create mode 100644 java/ql/src/Metrics/RefTypes/TInheritanceDepth.ql create mode 100644 java/ql/src/Metrics/RefTypes/TInheritanceDepth_MergeIntoSuperclassAfter.gv create mode 100644 java/ql/src/Metrics/RefTypes/TInheritanceDepth_MergeIntoSuperclassAfter.png create mode 100644 java/ql/src/Metrics/RefTypes/TInheritanceDepth_MergeIntoSuperclassBefore.gv create mode 100644 java/ql/src/Metrics/RefTypes/TInheritanceDepth_MergeIntoSuperclassBefore.png create mode 100644 java/ql/src/Metrics/RefTypes/TInheritanceDepth_UseComponentsAfter.gv create mode 100644 java/ql/src/Metrics/RefTypes/TInheritanceDepth_UseComponentsAfter.png create mode 100644 java/ql/src/Metrics/RefTypes/TInheritanceDepth_UseComponentsBefore.gv create mode 100644 java/ql/src/Metrics/RefTypes/TInheritanceDepth_UseComponentsBefore.png create mode 100644 java/ql/src/Metrics/RefTypes/TLackOfCohesionCK.qhelp create mode 100644 java/ql/src/Metrics/RefTypes/TLackOfCohesionCK.ql create mode 100644 java/ql/src/Metrics/RefTypes/TLackOfCohesionHS.qhelp create mode 100644 java/ql/src/Metrics/RefTypes/TLackOfCohesionHS.ql create mode 100644 java/ql/src/Metrics/RefTypes/TLinesOfCode.qhelp create mode 100644 java/ql/src/Metrics/RefTypes/TLinesOfCode.ql create mode 100644 java/ql/src/Metrics/RefTypes/TLinesOfComment.qhelp create mode 100644 java/ql/src/Metrics/RefTypes/TLinesOfComment.ql create mode 100644 java/ql/src/Metrics/RefTypes/TNumberOfCallables.qhelp create mode 100644 java/ql/src/Metrics/RefTypes/TNumberOfCallables.ql create mode 100644 java/ql/src/Metrics/RefTypes/TNumberOfFields.java create mode 100644 java/ql/src/Metrics/RefTypes/TNumberOfFields.qhelp create mode 100644 java/ql/src/Metrics/RefTypes/TNumberOfFields.ql create mode 100644 java/ql/src/Metrics/RefTypes/TNumberOfFieldsGood.java create mode 100644 java/ql/src/Metrics/RefTypes/TNumberOfStatements.qhelp create mode 100644 java/ql/src/Metrics/RefTypes/TNumberOfStatements.ql create mode 100644 java/ql/src/Metrics/RefTypes/TPercentageOfComments.qhelp create mode 100644 java/ql/src/Metrics/RefTypes/TPercentageOfComments.ql create mode 100644 java/ql/src/Metrics/RefTypes/TPercentageOfComplexCode.qhelp create mode 100644 java/ql/src/Metrics/RefTypes/TPercentageOfComplexCode.ql create mode 100644 java/ql/src/Metrics/RefTypes/TResponse.qhelp create mode 100644 java/ql/src/Metrics/RefTypes/TResponse.ql create mode 100644 java/ql/src/Metrics/RefTypes/TSelfContainedness.qhelp create mode 100644 java/ql/src/Metrics/RefTypes/TSelfContainedness.ql create mode 100644 java/ql/src/Metrics/RefTypes/TSizeOfAPI.qhelp create mode 100644 java/ql/src/Metrics/RefTypes/TSizeOfAPI.ql create mode 100644 java/ql/src/Metrics/RefTypes/TSpecialisationIndex.java create mode 100644 java/ql/src/Metrics/RefTypes/TSpecialisationIndex.qhelp create mode 100644 java/ql/src/Metrics/RefTypes/TSpecialisationIndex.ql create mode 100644 java/ql/src/Metrics/RefTypes/TSpecialisationIndexGood.java create mode 100644 java/ql/src/Metrics/queries.xml create mode 100644 java/ql/src/Performance/ConcatenationInLoops.java create mode 100644 java/ql/src/Performance/ConcatenationInLoops.qhelp create mode 100644 java/ql/src/Performance/ConcatenationInLoops.ql create mode 100644 java/ql/src/Performance/InefficientEmptyStringTest.java create mode 100644 java/ql/src/Performance/InefficientEmptyStringTest.qhelp create mode 100644 java/ql/src/Performance/InefficientEmptyStringTest.ql create mode 100644 java/ql/src/Performance/InefficientKeySetIterator.java create mode 100644 java/ql/src/Performance/InefficientKeySetIterator.qhelp create mode 100644 java/ql/src/Performance/InefficientKeySetIterator.ql create mode 100644 java/ql/src/Performance/InefficientOutputStream.qhelp create mode 100644 java/ql/src/Performance/InefficientOutputStream.ql create mode 100644 java/ql/src/Performance/InefficientOutputStreamBad.java create mode 100644 java/ql/src/Performance/InefficientOutputStreamGood.java create mode 100644 java/ql/src/Performance/InefficientPrimConstructor.java create mode 100644 java/ql/src/Performance/InefficientPrimConstructor.qhelp create mode 100644 java/ql/src/Performance/InefficientPrimConstructor.ql create mode 100644 java/ql/src/Performance/InefficientToArray.java create mode 100644 java/ql/src/Performance/InefficientToArray.qhelp create mode 100644 java/ql/src/Performance/InefficientToArray.ql create mode 100644 java/ql/src/Performance/InnerClassCouldBeStatic.qhelp create mode 100644 java/ql/src/Performance/InnerClassCouldBeStatic.ql create mode 100644 java/ql/src/Performance/NewStringString.java create mode 100644 java/ql/src/Performance/NewStringString.qhelp create mode 100644 java/ql/src/Performance/NewStringString.ql create mode 100644 java/ql/src/Security/CWE/CWE-022/PathsCommon.qll create mode 100644 java/ql/src/Security/CWE/CWE-022/TaintedPath.java create mode 100644 java/ql/src/Security/CWE/CWE-022/TaintedPath.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-022/TaintedPath.ql create mode 100644 java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.ql create mode 100644 java/ql/src/Security/CWE/CWE-078/ExecCommon.qll create mode 100644 java/ql/src/Security/CWE/CWE-078/ExecRelative.java create mode 100644 java/ql/src/Security/CWE/CWE-078/ExecRelative.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-078/ExecRelative.ql create mode 100644 java/ql/src/Security/CWE/CWE-078/ExecTainted.java create mode 100644 java/ql/src/Security/CWE/CWE-078/ExecTainted.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-078/ExecTainted.ql create mode 100644 java/ql/src/Security/CWE/CWE-078/ExecTaintedLocal.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-078/ExecTaintedLocal.ql create mode 100644 java/ql/src/Security/CWE/CWE-078/ExecUnescaped.java create mode 100644 java/ql/src/Security/CWE/CWE-078/ExecUnescaped.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-078/ExecUnescaped.ql create mode 100644 java/ql/src/Security/CWE/CWE-079/XSS.java create mode 100644 java/ql/src/Security/CWE/CWE-079/XSS.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-079/XSS.ql create mode 100644 java/ql/src/Security/CWE/CWE-079/XSSLocal.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-079/XSSLocal.ql create mode 100644 java/ql/src/Security/CWE/CWE-089/HowToAddress.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-089/SqlInjectionLib.qll create mode 100644 java/ql/src/Security/CWE/CWE-089/SqlTainted.java create mode 100644 java/ql/src/Security/CWE/CWE-089/SqlTainted.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-089/SqlTainted.ql create mode 100644 java/ql/src/Security/CWE/CWE-089/SqlTaintedLocal.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-089/SqlTaintedLocal.ql create mode 100644 java/ql/src/Security/CWE/CWE-089/SqlTaintedPersistence.java create mode 100644 java/ql/src/Security/CWE/CWE-089/SqlUnescaped.java create mode 100644 java/ql/src/Security/CWE/CWE-089/SqlUnescaped.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-089/SqlUnescaped.ql create mode 100644 java/ql/src/Security/CWE/CWE-113/ResponseSplitting.java create mode 100644 java/ql/src/Security/CWE/CWE-113/ResponseSplitting.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-113/ResponseSplitting.ql create mode 100644 java/ql/src/Security/CWE/CWE-113/ResponseSplitting.qll create mode 100644 java/ql/src/Security/CWE/CWE-113/ResponseSplittingLocal.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-113/ResponseSplittingLocal.ql create mode 100644 java/ql/src/Security/CWE/CWE-129/ArraySizing.qll create mode 100644 java/ql/src/Security/CWE/CWE-129/BoundingChecks.qll create mode 100644 java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstruction.java create mode 100644 java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstruction.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstruction.ql create mode 100644 java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstructionCodeSpecified.java create mode 100644 java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstructionCodeSpecified.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstructionCodeSpecified.ql create mode 100644 java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstructionLocal.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstructionLocal.ql create mode 100644 java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndex.java create mode 100644 java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndex.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndex.ql create mode 100644 java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndexCodeSpecified.java create mode 100644 java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndexCodeSpecified.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndexCodeSpecified.ql create mode 100644 java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndexLocal.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndexLocal.ql create mode 100644 java/ql/src/Security/CWE/CWE-134/ExternallyControlledFormatString.java create mode 100644 java/ql/src/Security/CWE/CWE-134/ExternallyControlledFormatString.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-134/ExternallyControlledFormatString.ql create mode 100644 java/ql/src/Security/CWE/CWE-134/ExternallyControlledFormatStringLocal.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-134/ExternallyControlledFormatStringLocal.ql create mode 100644 java/ql/src/Security/CWE/CWE-190/ArithmeticCommon.qll create mode 100644 java/ql/src/Security/CWE/CWE-190/ArithmeticTainted.java create mode 100644 java/ql/src/Security/CWE/CWE-190/ArithmeticTainted.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-190/ArithmeticTainted.ql create mode 100644 java/ql/src/Security/CWE/CWE-190/ArithmeticTaintedLocal.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-190/ArithmeticTaintedLocal.ql create mode 100644 java/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.java create mode 100644 java/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.ql create mode 100644 java/ql/src/Security/CWE/CWE-190/ArithmeticWithExtremeValues.java create mode 100644 java/ql/src/Security/CWE/CWE-190/ArithmeticWithExtremeValues.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-190/ArithmeticWithExtremeValues.ql create mode 100644 java/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.java create mode 100644 java/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.ql create mode 100644 java/ql/src/Security/CWE/CWE-209/StackTraceExposure.java create mode 100644 java/ql/src/Security/CWE/CWE-209/StackTraceExposure.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-209/StackTraceExposure.ql create mode 100644 java/ql/src/Security/CWE/CWE-312/CleartextStorage.java create mode 100644 java/ql/src/Security/CWE/CWE-312/CleartextStorage.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-312/CleartextStorageClass.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-312/CleartextStorageClass.ql create mode 100644 java/ql/src/Security/CWE/CWE-312/CleartextStorageCookie.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-312/CleartextStorageCookie.ql create mode 100644 java/ql/src/Security/CWE/CWE-312/CleartextStorageProperties.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-312/CleartextStorageProperties.ql create mode 100644 java/ql/src/Security/CWE/CWE-312/SensitiveStorage.qll create mode 100644 java/ql/src/Security/CWE/CWE-319/HttpsUrls.java create mode 100644 java/ql/src/Security/CWE/CWE-319/HttpsUrls.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-319/HttpsUrls.ql create mode 100644 java/ql/src/Security/CWE/CWE-319/UseSSL.java create mode 100644 java/ql/src/Security/CWE/CWE-319/UseSSL.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-319/UseSSL.ql create mode 100644 java/ql/src/Security/CWE/CWE-319/UseSSLSocketFactories.java create mode 100644 java/ql/src/Security/CWE/CWE-319/UseSSLSocketFactories.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-319/UseSSLSocketFactories.ql create mode 100644 java/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.java create mode 100644 java/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.ql create mode 100644 java/ql/src/Security/CWE/CWE-327/MaybeBrokenCryptoAlgorithm.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-327/MaybeBrokenCryptoAlgorithm.ql create mode 100644 java/ql/src/Security/CWE/CWE-335/PredictableSeed.java create mode 100644 java/ql/src/Security/CWE/CWE-335/PredictableSeed.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-335/PredictableSeed.ql create mode 100644 java/ql/src/Security/CWE/CWE-367/TOCTOURace.java create mode 100644 java/ql/src/Security/CWE/CWE-367/TOCTOURace.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-367/TOCTOURace.ql create mode 100644 java/ql/src/Security/CWE/CWE-421/SocketAuthRace.java create mode 100644 java/ql/src/Security/CWE/CWE-421/SocketAuthRace.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-421/SocketAuthRace.ql create mode 100644 java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.ql create mode 100644 java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.qll create mode 100644 java/ql/src/Security/CWE/CWE-502/UnsafeDeserializationBad.java create mode 100644 java/ql/src/Security/CWE/CWE-502/UnsafeDeserializationGood.java create mode 100644 java/ql/src/Security/CWE/CWE-601/UrlRedirect.java create mode 100644 java/ql/src/Security/CWE/CWE-601/UrlRedirect.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-601/UrlRedirect.ql create mode 100644 java/ql/src/Security/CWE/CWE-601/UrlRedirect.qll create mode 100644 java/ql/src/Security/CWE/CWE-601/UrlRedirectLocal.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-601/UrlRedirectLocal.ql create mode 100644 java/ql/src/Security/CWE/CWE-611/XXE.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-611/XXE.ql create mode 100644 java/ql/src/Security/CWE/CWE-611/XXEBad.java create mode 100644 java/ql/src/Security/CWE/CWE-611/XXEGood.java create mode 100644 java/ql/src/Security/CWE/CWE-611/XmlParsers.qll create mode 100644 java/ql/src/Security/CWE/CWE-614/InsecureCookie.java create mode 100644 java/ql/src/Security/CWE/CWE-614/InsecureCookie.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-614/InsecureCookie.ql create mode 100644 java/ql/src/Security/CWE/CWE-676/PotentiallyDangerousFunction.java create mode 100644 java/ql/src/Security/CWE/CWE-676/PotentiallyDangerousFunction.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-676/PotentiallyDangerousFunction.ql create mode 100644 java/ql/src/Security/CWE/CWE-681/NumericCastCommon.qll create mode 100644 java/ql/src/Security/CWE/CWE-681/NumericCastTainted.java create mode 100644 java/ql/src/Security/CWE/CWE-681/NumericCastTainted.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-681/NumericCastTainted.ql create mode 100644 java/ql/src/Security/CWE/CWE-681/NumericCastTaintedLocal.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-681/NumericCastTaintedLocal.ql create mode 100644 java/ql/src/Security/CWE/CWE-732/ReadingFromWorldWritableFile.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-732/ReadingFromWorldWritableFile.ql create mode 100644 java/ql/src/Security/CWE/CWE-798/HardcodedCredentials.qll create mode 100644 java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.java create mode 100644 java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.ql create mode 100644 java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsComparison.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsComparison.ql create mode 100644 java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsSourceCall.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsSourceCall.ql create mode 100644 java/ql/src/Security/CWE/CWE-798/HardcodedPasswordField.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-798/HardcodedPasswordField.ql create mode 100644 java/ql/src/Security/CWE/CWE-798/SensitiveApi.qll create mode 100644 java/ql/src/Security/CWE/CWE-807/ConditionalBypass.java create mode 100644 java/ql/src/Security/CWE/CWE-807/ConditionalBypass.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-807/ConditionalBypass.ql create mode 100644 java/ql/src/Security/CWE/CWE-807/TaintedPermissionsCheck.java create mode 100644 java/ql/src/Security/CWE/CWE-807/TaintedPermissionsCheck.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-807/TaintedPermissionsCheck.ql create mode 100644 java/ql/src/Security/CWE/CWE-833/LockOrderInconsistency.java create mode 100644 java/ql/src/Security/CWE/CWE-833/LockOrderInconsistency.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-833/LockOrderInconsistency.ql create mode 100644 java/ql/src/Security/CWE/CWE-835/InfiniteLoop.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-835/InfiniteLoop.ql create mode 100644 java/ql/src/Security/CWE/CWE-835/InfiniteLoopBad.java create mode 100644 java/ql/src/Security/CWE/CWE-835/InfiniteLoopGood.java create mode 100644 java/ql/src/Violations of Best Practice/Boolean Logic/SimplifyBoolExpr.java create mode 100644 java/ql/src/Violations of Best Practice/Boolean Logic/SimplifyBoolExpr.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Boolean Logic/SimplifyBoolExpr.ql create mode 100644 java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariable.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariable.ql create mode 100644 java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariableBad.java create mode 100644 java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariableGood.java create mode 100644 java/ql/src/Violations of Best Practice/Comments/CommentedCode.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Comments/CommentedCode.ql create mode 100644 java/ql/src/Violations of Best Practice/Comments/CommentedCode.qll create mode 100644 java/ql/src/Violations of Best Practice/Comments/TodoComments.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Comments/TodoComments.ql create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/AssignmentInReturn.java create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/AssignmentInReturn.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/AssignmentInReturn.ql create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/CreatesEmptyZip.java create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/CreatesEmptyZip.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/CreatesEmptyZip.ql create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/DeadLocals.qll create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/DeadRefTypes.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/DeadRefTypes.ql create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.java create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.ql create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocalUnread.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocalUnread.ql create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/EmptyFinalize.java create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/EmptyFinalize.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/EmptyFinalize.ql create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/FinalizerNullsFields.java create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/FinalizerNullsFields.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/FinalizerNullsFields.ql create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/InterfaceCannotBeImplemented.java create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/InterfaceCannotBeImplemented.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/InterfaceCannotBeImplemented.ql create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/LocalInitialisedButNotUsed.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/LocalInitialisedButNotUsed.ql create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/LocalNotRead.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/LocalNotRead.ql create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/NonAssignedFields.java create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/NonAssignedFields.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/NonAssignedFields.ql create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/PointlessForwardingMethod.java create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/PointlessForwardingMethod.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/PointlessForwardingMethod.ql create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.java create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.ql create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/UnusedField.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/UnusedField.ql create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/UnusedLabel.java create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/UnusedLabel.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/UnusedLabel.ql create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.ql create mode 100644 java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.java create mode 100644 java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.ql create mode 100644 java/ql/src/Violations of Best Practice/Declarations/Common.qll create mode 100644 java/ql/src/Violations of Best Practice/Declarations/MakeImportsExplicit.java create mode 100644 java/ql/src/Violations of Best Practice/Declarations/MakeImportsExplicit.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Declarations/MakeImportsExplicit.ql create mode 100644 java/ql/src/Violations of Best Practice/Declarations/NoConstantsOnly.java create mode 100644 java/ql/src/Violations of Best Practice/Declarations/NoConstantsOnly.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Declarations/NoConstantsOnly.ql create mode 100644 java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions-comment.java create mode 100644 java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions-good.java create mode 100644 java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions-ignore.java create mode 100644 java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions.java create mode 100644 java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions.ql create mode 100644 java/ql/src/Violations of Best Practice/Exception Handling/ExceptionCatch.java create mode 100644 java/ql/src/Violations of Best Practice/Exception Handling/ExceptionCatch.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Exception Handling/ExceptionCatch.ql create mode 100644 java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.java create mode 100644 java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.ql create mode 100644 java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.java create mode 100644 java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.ql create mode 100644 java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.java create mode 100644 java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.ql create mode 100644 java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResource.java create mode 100644 java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResource.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResource.ql create mode 100644 java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResourceGood.java create mode 100644 java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArray.java create mode 100644 java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArray.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArray.ql create mode 100644 java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArrayGood.java create mode 100644 java/ql/src/Violations of Best Practice/Magic Constants/MagicConstants.qll create mode 100644 java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsNumbers.java create mode 100644 java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsNumbers.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsNumbers.ql create mode 100644 java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsString.java create mode 100644 java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsString.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsString.ql create mode 100644 java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConstant.java create mode 100644 java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConstant.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConstant.ql create mode 100644 java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.java create mode 100644 java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.ql create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.java create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.ql create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.java create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.ql create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.java create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.ql create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/FieldMasksSuperField.java create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/FieldMasksSuperField.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/FieldMasksSuperField.ql create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsField.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsField.ql create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.java create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.ql create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.java create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.ql create mode 100644 java/ql/src/Violations of Best Practice/Naming Conventions/Shadowing.qll create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.java create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.ql create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExitGood.java create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.java create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.ql create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.java create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.ql create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.java create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.ql create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToStringGood.java create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.java create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.ql create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/NextFromIterator.java create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/NextFromIterator.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/NextFromIterator.ql create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.java create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.qhelp create mode 100644 java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.ql create mode 100644 java/ql/src/Violations of Best Practice/legacy/AutoBoxing.java create mode 100644 java/ql/src/Violations of Best Practice/legacy/AutoBoxing.qhelp create mode 100644 java/ql/src/Violations of Best Practice/legacy/AutoBoxing.ql create mode 100644 java/ql/src/Violations of Best Practice/legacy/FinallyMayNotComplete.qhelp create mode 100644 java/ql/src/Violations of Best Practice/legacy/FinallyMayNotComplete.ql create mode 100644 java/ql/src/Violations of Best Practice/legacy/InexactVarArg.java create mode 100644 java/ql/src/Violations of Best Practice/legacy/InexactVarArg.qhelp create mode 100644 java/ql/src/Violations of Best Practice/legacy/InexactVarArg.ql create mode 100644 java/ql/src/Violations of Best Practice/legacy/ParameterAssignment.java create mode 100644 java/ql/src/Violations of Best Practice/legacy/ParameterAssignment.qhelp create mode 100644 java/ql/src/Violations of Best Practice/legacy/ParameterAssignment.ql create mode 100644 java/ql/src/Violations of Best Practice/legacy/UnnecessaryCast.java create mode 100644 java/ql/src/Violations of Best Practice/legacy/UnnecessaryCast.qhelp create mode 100644 java/ql/src/Violations of Best Practice/legacy/UnnecessaryCast.ql create mode 100644 java/ql/src/Violations of Best Practice/legacy/UnnecessaryImport.qhelp create mode 100644 java/ql/src/Violations of Best Practice/legacy/UnnecessaryImport.ql create mode 100755 java/ql/src/config/semmlecode.dbscheme create mode 100644 java/ql/src/config/semmlecode.dbscheme.stats create mode 100644 java/ql/src/default.qll create mode 100644 java/ql/src/definitions.ql create mode 100644 java/ql/src/external/Clover.qll create mode 100644 java/ql/src/external/CodeDuplication.qll create mode 100644 java/ql/src/external/DefectFilter.qll create mode 100644 java/ql/src/external/DuplicateAnonymous.java create mode 100644 java/ql/src/external/DuplicateAnonymous.qhelp create mode 100644 java/ql/src/external/DuplicateAnonymous.ql create mode 100644 java/ql/src/external/DuplicateBlock.ql create mode 100644 java/ql/src/external/DuplicateMethod.java create mode 100644 java/ql/src/external/DuplicateMethod.qhelp create mode 100644 java/ql/src/external/DuplicateMethod.ql create mode 100644 java/ql/src/external/ExternalArtifact.qll create mode 100644 java/ql/src/external/MetricFilter.qll create mode 100644 java/ql/src/external/MostlyDuplicateClass.qhelp create mode 100644 java/ql/src/external/MostlyDuplicateClass.ql create mode 100644 java/ql/src/external/MostlyDuplicateFile.qhelp create mode 100644 java/ql/src/external/MostlyDuplicateFile.ql create mode 100644 java/ql/src/external/MostlyDuplicateMethod.qhelp create mode 100644 java/ql/src/external/MostlyDuplicateMethod.ql create mode 100644 java/ql/src/external/MostlySimilarFile.qhelp create mode 100644 java/ql/src/external/MostlySimilarFile.ql create mode 100644 java/ql/src/external/VCS.qll create mode 100644 java/ql/src/filters/ClassifyFiles.ql create mode 100644 java/ql/src/filters/FromSource.ql create mode 100644 java/ql/src/filters/ImportAdditionalLibraries.ql create mode 100644 java/ql/src/filters/NotGenerated.ql create mode 100644 java/ql/src/filters/NotGeneratedForMetric.ql create mode 100644 java/ql/src/filters/RecentDefects.ql create mode 100644 java/ql/src/filters/RecentDefectsForMetric.ql create mode 100644 java/ql/src/filters/SuppressionComment.ql create mode 100644 java/ql/src/java.qll create mode 100644 java/ql/src/meta/ssa/AmbiguousToString.ql create mode 100644 java/ql/src/meta/ssa/TooFewPhiInputs.ql create mode 100644 java/ql/src/meta/ssa/UncertainDefWithoutPrior.ql create mode 100644 java/ql/src/meta/ssa/UseWithoutUniqueSsaVariable.ql create mode 100644 java/ql/src/plugin.xml create mode 100644 java/ql/src/queries.xml create mode 100755 java/ql/src/semmle/code/FileSystem.qll create mode 100755 java/ql/src/semmle/code/Location.qll create mode 100755 java/ql/src/semmle/code/java/Annotation.qll create mode 100644 java/ql/src/semmle/code/java/Collections.qll create mode 100755 java/ql/src/semmle/code/java/CompilationUnit.qll create mode 100644 java/ql/src/semmle/code/java/Completion.qll create mode 100644 java/ql/src/semmle/code/java/Concurrency.qll create mode 100644 java/ql/src/semmle/code/java/ControlFlowGraph.qll create mode 100644 java/ql/src/semmle/code/java/Conversions.qll create mode 100755 java/ql/src/semmle/code/java/Dependency.qll create mode 100644 java/ql/src/semmle/code/java/DependencyCounts.qll create mode 100755 java/ql/src/semmle/code/java/Element.qll create mode 100755 java/ql/src/semmle/code/java/Exception.qll create mode 100755 java/ql/src/semmle/code/java/Expr.qll create mode 100644 java/ql/src/semmle/code/java/GeneratedFiles.qll create mode 100755 java/ql/src/semmle/code/java/Generics.qll create mode 100755 java/ql/src/semmle/code/java/Import.qll create mode 100755 java/ql/src/semmle/code/java/J2EE.qll create mode 100644 java/ql/src/semmle/code/java/JDK.qll create mode 100644 java/ql/src/semmle/code/java/JDKAnnotations.qll create mode 100644 java/ql/src/semmle/code/java/JMX.qll create mode 100755 java/ql/src/semmle/code/java/Javadoc.qll create mode 100644 java/ql/src/semmle/code/java/Maps.qll create mode 100755 java/ql/src/semmle/code/java/Member.qll create mode 100755 java/ql/src/semmle/code/java/Modifier.qll create mode 100755 java/ql/src/semmle/code/java/Modules.qll create mode 100755 java/ql/src/semmle/code/java/Package.qll create mode 100644 java/ql/src/semmle/code/java/Reflection.qll create mode 100644 java/ql/src/semmle/code/java/Serializability.qll create mode 100755 java/ql/src/semmle/code/java/Statement.qll create mode 100644 java/ql/src/semmle/code/java/StringFormat.qll create mode 100755 java/ql/src/semmle/code/java/Type.qll create mode 100644 java/ql/src/semmle/code/java/UnitTests.qll create mode 100755 java/ql/src/semmle/code/java/Variable.qll create mode 100644 java/ql/src/semmle/code/java/arithmetic/Overflow.qll create mode 100644 java/ql/src/semmle/code/java/comparison/Comparison.qll create mode 100644 java/ql/src/semmle/code/java/controlflow/BasicBlocks.qll create mode 100644 java/ql/src/semmle/code/java/controlflow/Dominance.qll create mode 100644 java/ql/src/semmle/code/java/controlflow/Guards.qll create mode 100644 java/ql/src/semmle/code/java/controlflow/Paths.qll create mode 100644 java/ql/src/semmle/code/java/controlflow/UnreachableBlocks.qll create mode 100644 java/ql/src/semmle/code/java/controlflow/internal/GuardsLogic.qll create mode 100644 java/ql/src/semmle/code/java/controlflow/unreachableblocks/ExcludeDebuggingProfilingLogging.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/DataFlow.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/DataFlow2.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/DataFlow3.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/DataFlow4.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/DataFlow5.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/DefUse.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/FlowSources.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/Guards.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/InstanceAccess.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/IntegerGuards.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/NullGuards.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/Nullness.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/RangeUtils.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/SSA.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/SignAnalysis.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/TaintTracking.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/TypeFlow.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/internal/BaseSSA.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/internal/DataFlowDispatch.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplDepr.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll create mode 100644 java/ql/src/semmle/code/java/dataflow/internal/DataFlowUtil.qll create mode 100644 java/ql/src/semmle/code/java/deadcode/DeadCode.qll create mode 100644 java/ql/src/semmle/code/java/deadcode/DeadCodeCustomizations.qll create mode 100644 java/ql/src/semmle/code/java/deadcode/DeadEnumConstant.qll create mode 100644 java/ql/src/semmle/code/java/deadcode/DeadField.qll create mode 100644 java/ql/src/semmle/code/java/deadcode/EntryPoints.qll create mode 100644 java/ql/src/semmle/code/java/deadcode/SpringEntryPoints.qll create mode 100644 java/ql/src/semmle/code/java/deadcode/StrutsEntryPoints.qll create mode 100644 java/ql/src/semmle/code/java/deadcode/TestEntryPoints.qll create mode 100644 java/ql/src/semmle/code/java/deadcode/WebEntryPoints.qll create mode 100644 java/ql/src/semmle/code/java/deadcode/frameworks/CamelEntryPoints.qll create mode 100644 java/ql/src/semmle/code/java/deadcode/frameworks/FitNesseEntryPoints.qll create mode 100644 java/ql/src/semmle/code/java/deadcode/frameworks/GigaSpacesXAPEntryPoints.qll create mode 100644 java/ql/src/semmle/code/java/dispatch/DispatchFlow.qll create mode 100644 java/ql/src/semmle/code/java/dispatch/VirtualDispatch.qll create mode 100644 java/ql/src/semmle/code/java/dispatch/WrappedInvocation.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/ApacheHttp.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/Assertions.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/Camel.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/Cucumber.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/JAXB.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/JUnitAnnotations.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/JavaxAnnotations.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/JaxWS.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/Jdbc.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/Kryo.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/Lombok.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/Mockito.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/Networking.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/Properties.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/Rmi.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/Selenium.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/Servlets.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/SnakeYaml.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/XStream.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/android/Intent.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/android/SQLite.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/android/WebView.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/android/XmlParsing.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/apache/Exec.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/camel/CamelJavaAnnotations.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/camel/CamelJavaDSL.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/gigaspaces/GigaSpaces.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/google/GoogleHttpClientApi.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/gwt/GWT.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/gwt/GwtUiBinder.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/gwt/GwtUiBinderXml.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/gwt/GwtXml.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/j2objc/J2ObjC.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/jackson/JacksonSerializability.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/javaee/JavaServerFaces.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/javaee/Persistence.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/javaee/PersistenceXML.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/javaee/ejb/EJB.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/javaee/ejb/EJBJarXML.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/javaee/ejb/EJBRestrictions.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/javaee/jsf/JSFAnnotations.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/javaee/jsf/JSFFacesContextXML.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/Spring.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringAbstractRef.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringAlias.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringArgType.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringAttribute.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringAutowire.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringBean.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringBeanFile.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringBeanRefType.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringCamel.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringComponentScan.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringConstructorArg.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringController.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringDescription.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringEntry.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringFlex.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringIdRef.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringImport.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringInitializingBean.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringKey.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringList.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringListOrSet.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringLookupMethod.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringMap.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringMergable.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringMeta.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringNull.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringProfile.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringProp.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringProperty.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringProps.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringQualifier.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringRef.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringReplacedMethod.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringSet.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringValue.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/SpringXMLElement.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/metrics/MetricSpringBean.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/spring/metrics/MetricSpringBeanFile.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/struts/StrutsActions.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/struts/StrutsAnnotations.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/struts/StrutsConventions.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/struts/StrutsXML.qll create mode 100755 java/ql/src/semmle/code/java/metrics/MetricCallable.qll create mode 100755 java/ql/src/semmle/code/java/metrics/MetricElement.qll create mode 100755 java/ql/src/semmle/code/java/metrics/MetricField.qll create mode 100755 java/ql/src/semmle/code/java/metrics/MetricPackage.qll create mode 100755 java/ql/src/semmle/code/java/metrics/MetricRefType.qll create mode 100755 java/ql/src/semmle/code/java/metrics/MetricStmt.qll create mode 100644 java/ql/src/semmle/code/java/security/ControlledString.qll create mode 100644 java/ql/src/semmle/code/java/security/DataFlow.qll create mode 100644 java/ql/src/semmle/code/java/security/Encryption.qll create mode 100644 java/ql/src/semmle/code/java/security/ExternalProcess.qll create mode 100644 java/ql/src/semmle/code/java/security/FileReadWrite.qll create mode 100644 java/ql/src/semmle/code/java/security/FileWritable.qll create mode 100644 java/ql/src/semmle/code/java/security/Random.qll create mode 100644 java/ql/src/semmle/code/java/security/RelativePaths.qll create mode 100644 java/ql/src/semmle/code/java/security/SecurityTests.qll create mode 100644 java/ql/src/semmle/code/java/security/SensitiveActions.qll create mode 100644 java/ql/src/semmle/code/java/security/SqlUnescapedLib.qll create mode 100644 java/ql/src/semmle/code/java/security/Validation.qll create mode 100644 java/ql/src/semmle/code/java/security/XSS.qll create mode 100644 java/ql/src/semmle/code/xml/Ant.qll create mode 100644 java/ql/src/semmle/code/xml/MavenPom.qll create mode 100644 java/ql/src/semmle/code/xml/WebXML.qll create mode 100755 java/ql/src/semmle/code/xml/XML.qll create mode 100644 java/ql/src/semmle/files/FileSystem.qll create mode 100644 java/ql/test/library-tests/Encryption/Test.java create mode 100644 java/ql/test/library-tests/Encryption/blacklist.expected create mode 100644 java/ql/test/library-tests/Encryption/blacklist.ql create mode 100644 java/ql/test/library-tests/Encryption/whitelist.expected create mode 100644 java/ql/test/library-tests/Encryption/whitelist.ql create mode 100644 java/ql/test/library-tests/ExternalProcess/Test.java create mode 100644 java/ql/test/library-tests/ExternalProcess/argumentToExec.expected create mode 100644 java/ql/test/library-tests/ExternalProcess/argumentToExec.ql create mode 100644 java/ql/test/library-tests/ExternalProcess/options create mode 100644 java/ql/test/library-tests/GeneratedFiles/FacebookAutoGen.java create mode 100644 java/ql/test/library-tests/GeneratedFiles/FollowingCodeGenerated.java create mode 100644 java/ql/test/library-tests/GeneratedFiles/Generated.expected create mode 100644 java/ql/test/library-tests/GeneratedFiles/Generated.ql create mode 100644 java/ql/test/library-tests/GeneratedFiles/GeneratedClass.expected create mode 100644 java/ql/test/library-tests/GeneratedFiles/GeneratedClass.ql create mode 100644 java/ql/test/library-tests/GeneratedFiles/HasBeenGenerated.java create mode 100644 java/ql/test/library-tests/GeneratedFiles/JavaCharStream.java create mode 100644 java/ql/test/library-tests/GeneratedFiles/JavaParserTokenManager.java create mode 100644 java/ql/test/library-tests/GeneratedFiles/Mithra.java create mode 100644 java/ql/test/library-tests/GeneratedFiles/QLParser.java create mode 100644 java/ql/test/library-tests/GeneratedFiles/StandardCharsets.java create mode 100644 java/ql/test/library-tests/GeneratedFiles/Test.java create mode 100644 java/ql/test/library-tests/GeneratedFiles/Test2.java create mode 100644 java/ql/test/library-tests/GeneratedFiles/Test3.java create mode 100644 java/ql/test/library-tests/GeneratedFiles/test/ThriftCompiler.java create mode 100644 java/ql/test/library-tests/JDK/Main.expected create mode 100644 java/ql/test/library-tests/JDK/Main.ql create mode 100644 java/ql/test/library-tests/JDK/jdk/A.java create mode 100644 java/ql/test/library-tests/RelativePaths/Test.java create mode 100644 java/ql/test/library-tests/RelativePaths/relativePath.expected create mode 100644 java/ql/test/library-tests/RelativePaths/relativePath.ql create mode 100644 java/ql/test/library-tests/annotations/Annotations.expected create mode 100644 java/ql/test/library-tests/annotations/Annotations.ql create mode 100644 java/ql/test/library-tests/annotations/GetAnnotationValue.expected create mode 100644 java/ql/test/library-tests/annotations/GetAnnotationValue.ql create mode 100644 java/ql/test/library-tests/annotations/GetLibraryAnnotationElement.expected create mode 100644 java/ql/test/library-tests/annotations/GetLibraryAnnotationElement.ql create mode 100644 java/ql/test/library-tests/annotations/SuppressWarnings.expected create mode 100644 java/ql/test/library-tests/annotations/SuppressWarnings.ql create mode 100644 java/ql/test/library-tests/annotations/annotations/A.java create mode 100644 java/ql/test/library-tests/annotations/annotations/Ann.java create mode 100644 java/ql/test/library-tests/annotations/annotations/B.java create mode 100644 java/ql/test/library-tests/annotations/annotations/C.java create mode 100644 java/ql/test/library-tests/annotations/annotations/Container.java create mode 100644 java/ql/test/library-tests/annotations/annotations/FieldAnnotations.java create mode 100644 java/ql/test/library-tests/annotations/annotations/LocalVarAnnotations.java create mode 100644 java/ql/test/library-tests/annotations/annotations/Pair.java create mode 100644 java/ql/test/library-tests/annotations/annotations/ParameterAnnotations.java create mode 100644 java/ql/test/library-tests/annotations/annotations/SuppressWarningsExample.java create mode 100644 java/ql/test/library-tests/arrays/ArrayInits.expected create mode 100644 java/ql/test/library-tests/arrays/ArrayInits.ql create mode 100644 java/ql/test/library-tests/arrays/Dimension.expected create mode 100644 java/ql/test/library-tests/arrays/Dimension.ql create mode 100644 java/ql/test/library-tests/arrays/ElementType.expected create mode 100644 java/ql/test/library-tests/arrays/ElementType.ql create mode 100644 java/ql/test/library-tests/arrays/arrays/A.java create mode 100644 java/ql/test/library-tests/arrays/arrays/B.java create mode 100644 java/ql/test/library-tests/callgraph/HashCodeCallees.expected create mode 100644 java/ql/test/library-tests/callgraph/HashCodeCallees.ql create mode 100644 java/ql/test/library-tests/callgraph/callgraph/A.java create mode 100644 java/ql/test/library-tests/collections/MapMethods.expected create mode 100644 java/ql/test/library-tests/collections/MapMethods.ql create mode 100644 java/ql/test/library-tests/collections/Maps.expected create mode 100644 java/ql/test/library-tests/collections/Maps.ql create mode 100644 java/ql/test/library-tests/collections/collections/Test.java create mode 100644 java/ql/test/library-tests/commentedcode/CommentedCode.expected create mode 100644 java/ql/test/library-tests/commentedcode/CommentedCode.java create mode 100644 java/ql/test/library-tests/commentedcode/CommentedCode.ql create mode 100644 java/ql/test/library-tests/commentedcode/Test.java create mode 100644 java/ql/test/library-tests/commentedcode/options create mode 100644 java/ql/test/library-tests/comments/Test.java create mode 100644 java/ql/test/library-tests/comments/TestWindows.java create mode 100644 java/ql/test/library-tests/comments/toString.expected create mode 100644 java/ql/test/library-tests/comments/toString.ql create mode 100644 java/ql/test/library-tests/complexity/Complexity.expected create mode 100644 java/ql/test/library-tests/complexity/Complexity.java create mode 100644 java/ql/test/library-tests/complexity/Complexity.ql create mode 100644 java/ql/test/library-tests/constants/CompileTimeConstantExpr.expected create mode 100644 java/ql/test/library-tests/constants/CompileTimeConstantExpr.ql create mode 100644 java/ql/test/library-tests/constants/constants/Constants.java create mode 100644 java/ql/test/library-tests/constants/constants/Initializers.java create mode 100644 java/ql/test/library-tests/constants/constants/Values.java create mode 100644 java/ql/test/library-tests/constants/getBooleanValue.expected create mode 100644 java/ql/test/library-tests/constants/getBooleanValue.ql create mode 100644 java/ql/test/library-tests/constants/getInitializer.expected create mode 100644 java/ql/test/library-tests/constants/getInitializer.ql create mode 100644 java/ql/test/library-tests/constants/getIntValue.expected create mode 100644 java/ql/test/library-tests/constants/getIntValue.ql create mode 100644 java/ql/test/library-tests/constructors/ClassInstanceExpr.expected create mode 100644 java/ql/test/library-tests/constructors/ClassInstanceExpr.ql create mode 100644 java/ql/test/library-tests/constructors/ConstructorCalls.expected create mode 100644 java/ql/test/library-tests/constructors/ConstructorCalls.ql create mode 100644 java/ql/test/library-tests/constructors/InitMethods.expected create mode 100644 java/ql/test/library-tests/constructors/InitMethods.ql create mode 100644 java/ql/test/library-tests/constructors/constructors/A.java create mode 100644 java/ql/test/library-tests/controlflow/basic/Test.java create mode 100644 java/ql/test/library-tests/controlflow/basic/bbStmts.expected create mode 100644 java/ql/test/library-tests/controlflow/basic/bbStmts.ql create mode 100644 java/ql/test/library-tests/controlflow/basic/bbStrictDominance.expected create mode 100644 java/ql/test/library-tests/controlflow/basic/bbStrictDominance.ql create mode 100644 java/ql/test/library-tests/controlflow/basic/bbSuccessor.expected create mode 100644 java/ql/test/library-tests/controlflow/basic/bbSuccessor.ql create mode 100644 java/ql/test/library-tests/controlflow/basic/strictDominance.expected create mode 100644 java/ql/test/library-tests/controlflow/basic/strictDominance.ql create mode 100644 java/ql/test/library-tests/controlflow/basic/strictPostDominance.expected create mode 100644 java/ql/test/library-tests/controlflow/basic/strictPostDominance.ql create mode 100644 java/ql/test/library-tests/controlflow/dominance/Test.java create mode 100644 java/ql/test/library-tests/controlflow/dominance/Test2.java create mode 100644 java/ql/test/library-tests/controlflow/dominance/dominanceBad.expected create mode 100644 java/ql/test/library-tests/controlflow/dominance/dominanceBad.ql create mode 100644 java/ql/test/library-tests/controlflow/dominance/dominanceWrong.expected create mode 100644 java/ql/test/library-tests/controlflow/dominance/dominanceWrong.ql create mode 100644 java/ql/test/library-tests/controlflow/dominance/dominatedByStart.expected create mode 100644 java/ql/test/library-tests/controlflow/dominance/dominatedByStart.ql create mode 100644 java/ql/test/library-tests/controlflow/dominance/dominator.expected create mode 100644 java/ql/test/library-tests/controlflow/dominance/dominator.ql create mode 100644 java/ql/test/library-tests/controlflow/dominance/dominatorExists.expected create mode 100644 java/ql/test/library-tests/controlflow/dominance/dominatorExists.ql create mode 100644 java/ql/test/library-tests/controlflow/dominance/dominatorUnique.expected create mode 100644 java/ql/test/library-tests/controlflow/dominance/dominatorUnique.ql create mode 100644 java/ql/test/library-tests/controlflow/paths/A.java create mode 100644 java/ql/test/library-tests/controlflow/paths/paths.expected create mode 100644 java/ql/test/library-tests/controlflow/paths/paths.ql create mode 100644 java/ql/test/library-tests/dataflow/fields/A.java create mode 100644 java/ql/test/library-tests/dataflow/fields/B.java create mode 100644 java/ql/test/library-tests/dataflow/fields/flow.expected create mode 100644 java/ql/test/library-tests/dataflow/fields/flow.ql create mode 100644 java/ql/test/library-tests/dataflow/this-flow/A.java create mode 100644 java/ql/test/library-tests/dataflow/this-flow/this-flow.expected create mode 100644 java/ql/test/library-tests/dataflow/this-flow/this-flow.ql create mode 100644 java/ql/test/library-tests/defUse/Test.java create mode 100644 java/ql/test/library-tests/defUse/defUse.expected create mode 100644 java/ql/test/library-tests/defUse/defUse.ql create mode 100644 java/ql/test/library-tests/defUse/parameterUse.expected create mode 100644 java/ql/test/library-tests/defUse/parameterUse.ql create mode 100644 java/ql/test/library-tests/defUse/useUse.expected create mode 100644 java/ql/test/library-tests/defUse/useUse.ql create mode 100644 java/ql/test/library-tests/dependency-counts/Example.java create mode 100644 java/ql/test/library-tests/dependency-counts/NumDepends.expected create mode 100644 java/ql/test/library-tests/dependency-counts/NumDepends.ql create mode 100644 java/ql/test/library-tests/dependency/Depends.expected create mode 100644 java/ql/test/library-tests/dependency/Depends.ql create mode 100644 java/ql/test/library-tests/dependency/UsesType.expected create mode 100644 java/ql/test/library-tests/dependency/UsesType.ql create mode 100644 java/ql/test/library-tests/dependency/dependency/A.java create mode 100644 java/ql/test/library-tests/dispatch/Test.java create mode 100644 java/ql/test/library-tests/dispatch/ViableCallable.java create mode 100644 java/ql/test/library-tests/dispatch/ViableCallable2.java create mode 100644 java/ql/test/library-tests/dispatch/ViableCallable3.java create mode 100644 java/ql/test/library-tests/dispatch/ViableCallableA.java create mode 100644 java/ql/test/library-tests/dispatch/ViableCallableB.java create mode 100644 java/ql/test/library-tests/dispatch/viableCallable.expected create mode 100644 java/ql/test/library-tests/dispatch/viableCallable.ql create mode 100644 java/ql/test/library-tests/dispatch/virtualDispatch.expected create mode 100644 java/ql/test/library-tests/dispatch/virtualDispatch.ql create mode 100644 java/ql/test/library-tests/fields/FieldAnnotations.expected create mode 100644 java/ql/test/library-tests/fields/FieldAnnotations.ql create mode 100644 java/ql/test/library-tests/fields/FieldDecl.expected create mode 100644 java/ql/test/library-tests/fields/FieldDecl.ql create mode 100644 java/ql/test/library-tests/fields/FieldDeclLocation.expected create mode 100644 java/ql/test/library-tests/fields/FieldDeclLocation.ql create mode 100644 java/ql/test/library-tests/fields/FieldLocation.expected create mode 100644 java/ql/test/library-tests/fields/FieldLocation.ql create mode 100644 java/ql/test/library-tests/fields/fields/FieldTest.java create mode 100644 java/ql/test/library-tests/generics/BinaryTypeVars.expected create mode 100644 java/ql/test/library-tests/generics/BinaryTypeVars.ql create mode 100644 java/ql/test/library-tests/generics/ParameterizedClassInstanceExpressions.expected create mode 100644 java/ql/test/library-tests/generics/ParameterizedClassInstanceExpressions.ql create mode 100644 java/ql/test/library-tests/generics/SourceDeclaration.expected create mode 100644 java/ql/test/library-tests/generics/SourceDeclaration.ql create mode 100644 java/ql/test/library-tests/generics/TypeVarsUpperBound.expected create mode 100644 java/ql/test/library-tests/generics/TypeVarsUpperBound.ql create mode 100644 java/ql/test/library-tests/generics/WildcardsLowerBound.expected create mode 100644 java/ql/test/library-tests/generics/WildcardsLowerBound.ql create mode 100644 java/ql/test/library-tests/generics/generics/A.java create mode 100644 java/ql/test/library-tests/guards/Logic.java create mode 100644 java/ql/test/library-tests/guards/Test.java create mode 100644 java/ql/test/library-tests/guards/guards.expected create mode 100644 java/ql/test/library-tests/guards/guards.ql create mode 100644 java/ql/test/library-tests/guards/guardslogic.expected create mode 100644 java/ql/test/library-tests/guards/guardslogic.ql create mode 100644 java/ql/test/library-tests/gwt/JSNI.expected create mode 100644 java/ql/test/library-tests/gwt/JSNI.java create mode 100644 java/ql/test/library-tests/gwt/JSNI.ql create mode 100644 java/ql/test/library-tests/j2objc/OCNIComment.expected create mode 100644 java/ql/test/library-tests/j2objc/OCNIComment.ql create mode 100644 java/ql/test/library-tests/j2objc/Test.java create mode 100644 java/ql/test/library-tests/j2objc/options create mode 100644 java/ql/test/library-tests/java7/Diamond/Diamond.java create mode 100644 java/ql/test/library-tests/java7/Diamond/Diamonds.expected create mode 100644 java/ql/test/library-tests/java7/Diamond/Diamonds.ql create mode 100644 java/ql/test/library-tests/java7/MultiCatch/MultiCatch.expected create mode 100644 java/ql/test/library-tests/java7/MultiCatch/MultiCatch.java create mode 100644 java/ql/test/library-tests/java7/MultiCatch/MultiCatch.ql create mode 100644 java/ql/test/library-tests/java7/MultiCatch/MultiCatchControlFlow.expected create mode 100644 java/ql/test/library-tests/java7/MultiCatch/MultiCatchControlFlow.ql create mode 100644 java/ql/test/library-tests/javadoc/AllComments.expected create mode 100644 java/ql/test/library-tests/javadoc/AllComments.ql create mode 100644 java/ql/test/library-tests/javadoc/JavadocComments.expected create mode 100644 java/ql/test/library-tests/javadoc/JavadocComments.ql create mode 100644 java/ql/test/library-tests/javadoc/javadoc/Test.java create mode 100644 java/ql/test/library-tests/literals/literalBoolean.expected create mode 100644 java/ql/test/library-tests/literals/literalBoolean.ql create mode 100644 java/ql/test/library-tests/literals/literalChar.expected create mode 100644 java/ql/test/library-tests/literals/literalChar.ql create mode 100644 java/ql/test/library-tests/literals/literalDouble.expected create mode 100644 java/ql/test/library-tests/literals/literalDouble.ql create mode 100644 java/ql/test/library-tests/literals/literalFloat.expected create mode 100644 java/ql/test/library-tests/literals/literalFloat.ql create mode 100644 java/ql/test/library-tests/literals/literalInteger.expected create mode 100644 java/ql/test/library-tests/literals/literalInteger.ql create mode 100644 java/ql/test/library-tests/literals/literalLong.expected create mode 100644 java/ql/test/library-tests/literals/literalLong.ql create mode 100644 java/ql/test/library-tests/literals/literalString.expected create mode 100644 java/ql/test/library-tests/literals/literalString.ql create mode 100644 java/ql/test/library-tests/literals/literals/Literals.java create mode 100644 java/ql/test/library-tests/localvars/LocalVarDeclExprChildren.expected create mode 100644 java/ql/test/library-tests/localvars/LocalVarDeclExprChildren.ql create mode 100644 java/ql/test/library-tests/localvars/LocalVarDeclExprTypeAccess.expected create mode 100644 java/ql/test/library-tests/localvars/LocalVarDeclExprTypeAccess.ql create mode 100644 java/ql/test/library-tests/localvars/LocalVarDeclExprs.expected create mode 100644 java/ql/test/library-tests/localvars/LocalVarDeclExprs.ql create mode 100644 java/ql/test/library-tests/localvars/LocalVarDeclStmtChildren.expected create mode 100644 java/ql/test/library-tests/localvars/LocalVarDeclStmtChildren.ql create mode 100644 java/ql/test/library-tests/localvars/TypeAccesses.expected create mode 100644 java/ql/test/library-tests/localvars/TypeAccesses.ql create mode 100644 java/ql/test/library-tests/localvars/localvars/LocalVarTest.java create mode 100644 java/ql/test/library-tests/locations/EnumLocations.expected create mode 100644 java/ql/test/library-tests/locations/EnumLocations.ql create mode 100644 java/ql/test/library-tests/locations/ExceptionLocations.expected create mode 100644 java/ql/test/library-tests/locations/ExceptionLocations.ql create mode 100644 java/ql/test/library-tests/locations/LiteralLocations.expected create mode 100644 java/ql/test/library-tests/locations/LiteralLocations.ql create mode 100644 java/ql/test/library-tests/locations/NegativeLiteralLocation.expected create mode 100644 java/ql/test/library-tests/locations/NegativeLiteralLocation.ql create mode 100644 java/ql/test/library-tests/locations/NewLocations.expected create mode 100644 java/ql/test/library-tests/locations/NewLocations.ql create mode 100644 java/ql/test/library-tests/locations/TypeLocations.expected create mode 100644 java/ql/test/library-tests/locations/TypeLocations.ql create mode 100644 java/ql/test/library-tests/locations/WildcardLocations.expected create mode 100644 java/ql/test/library-tests/locations/WildcardLocations.ql create mode 100644 java/ql/test/library-tests/locations/locations/A.java create mode 100644 java/ql/test/library-tests/locations/locations/B.java create mode 100644 java/ql/test/library-tests/locations/locations/C.java create mode 100644 java/ql/test/library-tests/locations/locations/D.java create mode 100644 java/ql/test/library-tests/locations/locations/E.java create mode 100644 java/ql/test/library-tests/locations/locations/F.java create mode 100644 java/ql/test/library-tests/locations/locations/G.java create mode 100644 java/ql/test/library-tests/modifiers/EnumFinality.expected create mode 100644 java/ql/test/library-tests/modifiers/EnumFinality.ql create mode 100644 java/ql/test/library-tests/modifiers/Test.java create mode 100644 java/ql/test/library-tests/overrides/ConstructedOverrides.expected create mode 100644 java/ql/test/library-tests/overrides/ConstructedOverrides.java create mode 100644 java/ql/test/library-tests/overrides/ConstructedOverrides.ql create mode 100644 java/ql/test/library-tests/overrides/ConstructedOverrides2.expected create mode 100644 java/ql/test/library-tests/overrides/ConstructedOverrides2.ql create mode 100644 java/ql/test/library-tests/overriding/Method_getAPossibleImplementation.expected create mode 100644 java/ql/test/library-tests/overriding/Method_getAPossibleImplementation.ql create mode 100644 java/ql/test/library-tests/overriding/Method_getAnOverride.expected create mode 100644 java/ql/test/library-tests/overriding/Method_getAnOverride.ql create mode 100644 java/ql/test/library-tests/overriding/Test.java create mode 100644 java/ql/test/library-tests/qlengine/Tst.java create mode 100644 java/ql/test/library-tests/qlengine/castAtType.expected create mode 100644 java/ql/test/library-tests/qlengine/castAtType.ql create mode 100644 java/ql/test/library-tests/qlengine/fromAtType.expected create mode 100644 java/ql/test/library-tests/qlengine/fromAtType.ql create mode 100644 java/ql/test/library-tests/qlengine/instanceOfAtType.expected create mode 100644 java/ql/test/library-tests/qlengine/instanceOfAtType.ql create mode 100644 java/ql/test/library-tests/qlengine/selectAtType.expected create mode 100644 java/ql/test/library-tests/qlengine/selectAtType.ql create mode 100644 java/ql/test/library-tests/reflection/InferClassParameter.expected create mode 100644 java/ql/test/library-tests/reflection/InferClassParameter.ql create mode 100644 java/ql/test/library-tests/reflection/reflection/ReflectiveAccess.java create mode 100644 java/ql/test/library-tests/ssa-large/Large.java create mode 100644 java/ql/test/library-tests/ssa-large/countssa.expected create mode 100644 java/ql/test/library-tests/ssa-large/countssa.ql create mode 100644 java/ql/test/library-tests/ssa/Fields.java create mode 100644 java/ql/test/library-tests/ssa/Nested.java create mode 100644 java/ql/test/library-tests/ssa/Test.java create mode 100644 java/ql/test/library-tests/ssa/adjacentUses.expected create mode 100644 java/ql/test/library-tests/ssa/adjacentUses.ql create mode 100644 java/ql/test/library-tests/ssa/captures.expected create mode 100644 java/ql/test/library-tests/ssa/captures.ql create mode 100644 java/ql/test/library-tests/ssa/firstUse.expected create mode 100644 java/ql/test/library-tests/ssa/firstUse.ql create mode 100644 java/ql/test/library-tests/ssa/ssaDef.expected create mode 100644 java/ql/test/library-tests/ssa/ssaDef.ql create mode 100644 java/ql/test/library-tests/ssa/ssaPhi.expected create mode 100644 java/ql/test/library-tests/ssa/ssaPhi.ql create mode 100644 java/ql/test/library-tests/ssa/ssaUse.expected create mode 100644 java/ql/test/library-tests/ssa/ssaUse.ql create mode 100644 java/ql/test/library-tests/stmts/JumpTargets.expected create mode 100644 java/ql/test/library-tests/stmts/JumpTargets.ql create mode 100644 java/ql/test/library-tests/stmts/SwitchCases.expected create mode 100644 java/ql/test/library-tests/stmts/SwitchCases.ql create mode 100644 java/ql/test/library-tests/stmts/stmts/A.java create mode 100644 java/ql/test/library-tests/stmts/stmts/B.java create mode 100644 java/ql/test/library-tests/structure/DeclaresMember.expected create mode 100644 java/ql/test/library-tests/structure/DeclaresMember.ql create mode 100644 java/ql/test/library-tests/structure/EnclosingCallables.expected create mode 100644 java/ql/test/library-tests/structure/EnclosingCallables.ql create mode 100644 java/ql/test/library-tests/structure/EnclosingStatements.expected create mode 100644 java/ql/test/library-tests/structure/EnclosingStatements.ql create mode 100644 java/ql/test/library-tests/structure/HasMethod.expected create mode 100644 java/ql/test/library-tests/structure/HasMethod.ql create mode 100644 java/ql/test/library-tests/structure/HasObjectMethod.expected create mode 100644 java/ql/test/library-tests/structure/HasObjectMethod.ql create mode 100644 java/ql/test/library-tests/structure/InSameTopLevelType.expected create mode 100644 java/ql/test/library-tests/structure/InSameTopLevelType.ql create mode 100644 java/ql/test/library-tests/structure/IsInType.expected create mode 100644 java/ql/test/library-tests/structure/IsInType.ql create mode 100644 java/ql/test/library-tests/structure/IsNestedType.expected create mode 100644 java/ql/test/library-tests/structure/IsNestedType.ql create mode 100644 java/ql/test/library-tests/structure/IsTopLevelType.expected create mode 100644 java/ql/test/library-tests/structure/IsTopLevelType.ql create mode 100644 java/ql/test/library-tests/structure/OuterType.expected create mode 100644 java/ql/test/library-tests/structure/OuterType.ql create mode 100644 java/ql/test/library-tests/structure/TypeGetCompilationUnit.expected create mode 100644 java/ql/test/library-tests/structure/TypeGetCompilationUnit.ql create mode 100644 java/ql/test/library-tests/structure/TypeIsInPackage.expected create mode 100644 java/ql/test/library-tests/structure/TypeIsInPackage.ql create mode 100644 java/ql/test/library-tests/structure/structure/A.java create mode 100644 java/ql/test/library-tests/structure/structure/Inherit.java create mode 100644 java/ql/test/library-tests/successors/CloseReaderTest/CloseReaderTest.java create mode 100644 java/ql/test/library-tests/successors/CloseReaderTest/FalseSuccessors.expected create mode 100644 java/ql/test/library-tests/successors/CloseReaderTest/FalseSuccessors.ql create mode 100644 java/ql/test/library-tests/successors/CloseReaderTest/PopulateRuntimeException.java create mode 100644 java/ql/test/library-tests/successors/CloseReaderTest/TestSucc.expected create mode 100644 java/ql/test/library-tests/successors/CloseReaderTest/TestSucc.ql create mode 100644 java/ql/test/library-tests/successors/LoopVarReadTest/FalseSuccessors.expected create mode 100644 java/ql/test/library-tests/successors/LoopVarReadTest/FalseSuccessors.ql create mode 100644 java/ql/test/library-tests/successors/LoopVarReadTest/LoopVarReadTest.java create mode 100644 java/ql/test/library-tests/successors/LoopVarReadTest/PopulateRuntimeException.java create mode 100644 java/ql/test/library-tests/successors/LoopVarReadTest/TestSucc.expected create mode 100644 java/ql/test/library-tests/successors/LoopVarReadTest/TestSucc.ql create mode 100644 java/ql/test/library-tests/successors/SaveFileTest/FalseSuccessors.expected create mode 100644 java/ql/test/library-tests/successors/SaveFileTest/FalseSuccessors.ql create mode 100644 java/ql/test/library-tests/successors/SaveFileTest/PopulateRuntimeException.java create mode 100644 java/ql/test/library-tests/successors/SaveFileTest/SaveFileTest.java create mode 100644 java/ql/test/library-tests/successors/SaveFileTest/TestSucc.expected create mode 100644 java/ql/test/library-tests/successors/SaveFileTest/TestSucc.ql create mode 100644 java/ql/test/library-tests/successors/SchackTest/FalseSuccessors.expected create mode 100644 java/ql/test/library-tests/successors/SchackTest/FalseSuccessors.ql create mode 100644 java/ql/test/library-tests/successors/SchackTest/PopulateRuntimeException.java create mode 100644 java/ql/test/library-tests/successors/SchackTest/SchackTest.java create mode 100644 java/ql/test/library-tests/successors/SchackTest/TestSucc.expected create mode 100644 java/ql/test/library-tests/successors/SchackTest/TestSucc.ql create mode 100644 java/ql/test/library-tests/successors/TestBreak/FalseSuccessors.expected create mode 100644 java/ql/test/library-tests/successors/TestBreak/FalseSuccessors.ql create mode 100644 java/ql/test/library-tests/successors/TestBreak/PopulateRuntimeException.java create mode 100644 java/ql/test/library-tests/successors/TestBreak/TestBreak.java create mode 100644 java/ql/test/library-tests/successors/TestBreak/TestSucc.expected create mode 100644 java/ql/test/library-tests/successors/TestBreak/TestSucc.ql create mode 100644 java/ql/test/library-tests/successors/TestContinue/FalseSuccessors.expected create mode 100644 java/ql/test/library-tests/successors/TestContinue/FalseSuccessors.ql create mode 100644 java/ql/test/library-tests/successors/TestContinue/PopulateRuntimeException.java create mode 100644 java/ql/test/library-tests/successors/TestContinue/TestContinue.java create mode 100644 java/ql/test/library-tests/successors/TestContinue/TestSucc.expected create mode 100644 java/ql/test/library-tests/successors/TestContinue/TestSucc.ql create mode 100644 java/ql/test/library-tests/successors/TestDeclarations/FalseSuccessors.expected create mode 100644 java/ql/test/library-tests/successors/TestDeclarations/FalseSuccessors.ql create mode 100644 java/ql/test/library-tests/successors/TestDeclarations/PopulateRuntimeException.java create mode 100644 java/ql/test/library-tests/successors/TestDeclarations/TestDeclarations.java create mode 100644 java/ql/test/library-tests/successors/TestDeclarations/TestSucc.expected create mode 100644 java/ql/test/library-tests/successors/TestDeclarations/TestSucc.ql create mode 100644 java/ql/test/library-tests/successors/TestFinally/FalseSuccessors.expected create mode 100644 java/ql/test/library-tests/successors/TestFinally/FalseSuccessors.ql create mode 100644 java/ql/test/library-tests/successors/TestFinally/PopulateRuntimeException.java create mode 100644 java/ql/test/library-tests/successors/TestFinally/TestFinally.java create mode 100644 java/ql/test/library-tests/successors/TestFinally/TestSucc.expected create mode 100644 java/ql/test/library-tests/successors/TestFinally/TestSucc.ql create mode 100644 java/ql/test/library-tests/successors/TestFinallyBreakContinue/FalseSuccessors.expected create mode 100644 java/ql/test/library-tests/successors/TestFinallyBreakContinue/FalseSuccessors.ql create mode 100644 java/ql/test/library-tests/successors/TestFinallyBreakContinue/PopulateRuntimeException.java create mode 100644 java/ql/test/library-tests/successors/TestFinallyBreakContinue/TestFinallyBreakContinue.java create mode 100644 java/ql/test/library-tests/successors/TestFinallyBreakContinue/TestSucc.expected create mode 100644 java/ql/test/library-tests/successors/TestFinallyBreakContinue/TestSucc.ql create mode 100644 java/ql/test/library-tests/successors/TestLoopBranch/FalseSuccessors.expected create mode 100644 java/ql/test/library-tests/successors/TestLoopBranch/FalseSuccessors.ql create mode 100644 java/ql/test/library-tests/successors/TestLoopBranch/PopulateRuntimeException.java create mode 100644 java/ql/test/library-tests/successors/TestLoopBranch/TestLoopBranch.java create mode 100644 java/ql/test/library-tests/successors/TestLoopBranch/TestSucc.expected create mode 100644 java/ql/test/library-tests/successors/TestLoopBranch/TestSucc.ql create mode 100644 java/ql/test/library-tests/successors/TestThrow/FalseSuccessors.expected create mode 100644 java/ql/test/library-tests/successors/TestThrow/FalseSuccessors.ql create mode 100644 java/ql/test/library-tests/successors/TestThrow/PopulateRuntimeException.java create mode 100644 java/ql/test/library-tests/successors/TestThrow/TestSucc.expected create mode 100644 java/ql/test/library-tests/successors/TestThrow/TestSucc.ql create mode 100644 java/ql/test/library-tests/successors/TestThrow/TestThrow.java create mode 100644 java/ql/test/library-tests/successors/TestThrow2/FalseSuccessors.expected create mode 100644 java/ql/test/library-tests/successors/TestThrow2/FalseSuccessors.ql create mode 100644 java/ql/test/library-tests/successors/TestThrow2/PopulateRuntimeException.java create mode 100644 java/ql/test/library-tests/successors/TestThrow2/TestSucc.expected create mode 100644 java/ql/test/library-tests/successors/TestThrow2/TestSucc.ql create mode 100644 java/ql/test/library-tests/successors/TestThrow2/TestThrow2.java create mode 100644 java/ql/test/library-tests/successors/TestTryCatch/FalseSuccessors.expected create mode 100644 java/ql/test/library-tests/successors/TestTryCatch/FalseSuccessors.ql create mode 100644 java/ql/test/library-tests/successors/TestTryCatch/PopulateRuntimeException.java create mode 100644 java/ql/test/library-tests/successors/TestTryCatch/TestSucc.expected create mode 100644 java/ql/test/library-tests/successors/TestTryCatch/TestSucc.ql create mode 100644 java/ql/test/library-tests/successors/TestTryCatch/TestTryCatch.java create mode 100644 java/ql/test/library-tests/successors/TestTryWithResources/FalseSuccessors.expected create mode 100644 java/ql/test/library-tests/successors/TestTryWithResources/FalseSuccessors.ql create mode 100644 java/ql/test/library-tests/successors/TestTryWithResources/PopulateRuntimeException.java create mode 100644 java/ql/test/library-tests/successors/TestTryWithResources/TestSucc.expected create mode 100644 java/ql/test/library-tests/successors/TestTryWithResources/TestSucc.ql create mode 100644 java/ql/test/library-tests/successors/TestTryWithResources/TestTryWithResources.java create mode 100644 java/ql/test/library-tests/typeaccesses/ArrayTypeAccesses.expected create mode 100644 java/ql/test/library-tests/typeaccesses/ArrayTypeAccesses.ql create mode 100644 java/ql/test/library-tests/typeaccesses/TypeAccesses.expected create mode 100644 java/ql/test/library-tests/typeaccesses/TypeAccesses.ql create mode 100644 java/ql/test/library-tests/typeaccesses/typeaccesses/Arrays.java create mode 100644 java/ql/test/library-tests/typeaccesses/typeaccesses/Outer.java create mode 100644 java/ql/test/library-tests/typeaccesses/typeaccesses/TA.java create mode 100644 java/ql/test/library-tests/typeflow/A.java create mode 100644 java/ql/test/library-tests/typeflow/typeflow.expected create mode 100644 java/ql/test/library-tests/typeflow/typeflow.ql create mode 100644 java/ql/test/library-tests/types/A.java create mode 100644 java/ql/test/library-tests/types/FloatingPointTypes.expected create mode 100644 java/ql/test/library-tests/types/FloatingPointTypes.ql create mode 100644 java/ql/test/library-tests/types/IntegralTypes.expected create mode 100644 java/ql/test/library-tests/types/IntegralTypes.ql create mode 100644 java/ql/test/library-tests/types/NumericTypes.expected create mode 100644 java/ql/test/library-tests/types/NumericTypes.ql create mode 100644 java/ql/test/library-tests/unreachableblocks/UnreachableBlocks.expected create mode 100644 java/ql/test/library-tests/unreachableblocks/UnreachableBlocks.ql create mode 100644 java/ql/test/library-tests/unreachableblocks/unreachableblocks/Unreachable.java create mode 100644 java/ql/test/library-tests/varargs/Varargs.expected create mode 100644 java/ql/test/library-tests/varargs/Varargs.ql create mode 100644 java/ql/test/library-tests/varargs/varargs/Test.java create mode 100644 java/ql/test/query-tests/AlertSuppression/AlertSuppression.expected create mode 100644 java/ql/test/query-tests/AlertSuppression/AlertSuppression.qlref create mode 100644 java/ql/test/query-tests/AlertSuppression/Test.java create mode 100644 java/ql/test/query-tests/AlertSuppression/TestWindows.java create mode 100644 java/ql/test/query-tests/AutoBoxing/AutoBoxing.expected create mode 100644 java/ql/test/query-tests/AutoBoxing/AutoBoxing.qlref create mode 100644 java/ql/test/query-tests/AutoBoxing/Test.java create mode 100644 java/ql/test/query-tests/AvoidDeprecatedCallableAccess/AvoidDeprecatedCallableAccess.expected create mode 100644 java/ql/test/query-tests/AvoidDeprecatedCallableAccess/AvoidDeprecatedCallableAccess.qlref create mode 100644 java/ql/test/query-tests/AvoidDeprecatedCallableAccess/Test.java create mode 100644 java/ql/test/query-tests/BadCheckOdd/BadCheckOdd.expected create mode 100644 java/ql/test/query-tests/BadCheckOdd/BadCheckOdd.java create mode 100644 java/ql/test/query-tests/BadCheckOdd/BadCheckOdd.qlref create mode 100644 java/ql/test/query-tests/BoxedVariable/BoxedVariable.expected create mode 100644 java/ql/test/query-tests/BoxedVariable/BoxedVariable.java create mode 100644 java/ql/test/query-tests/BoxedVariable/BoxedVariable.qlref create mode 100644 java/ql/test/query-tests/BusyWait/BusyWait.expected create mode 100644 java/ql/test/query-tests/BusyWait/BusyWait.qlref create mode 100644 java/ql/test/query-tests/BusyWait/BusyWaits.java create mode 100644 java/ql/test/query-tests/CallsToRunnableRun/CallsToRunnableRun.expected create mode 100644 java/ql/test/query-tests/CallsToRunnableRun/CallsToRunnableRun.java create mode 100644 java/ql/test/query-tests/CallsToRunnableRun/CallsToRunnableRun.qlref create mode 100644 java/ql/test/query-tests/CloseResource/CloseReader/CloseReader.expected create mode 100644 java/ql/test/query-tests/CloseResource/CloseReader/CloseReader.java create mode 100644 java/ql/test/query-tests/CloseResource/CloseReader/CloseReader.qlref create mode 100644 java/ql/test/query-tests/CompareIdenticalValues/A.java create mode 100644 java/ql/test/query-tests/CompareIdenticalValues/CompareIdenticalValues.expected create mode 100644 java/ql/test/query-tests/CompareIdenticalValues/CompareIdenticalValues.qlref create mode 100644 java/ql/test/query-tests/ComplexCondition/ComplexCondition.expected create mode 100644 java/ql/test/query-tests/ComplexCondition/ComplexCondition.java create mode 100644 java/ql/test/query-tests/ComplexCondition/ComplexCondition.qlref create mode 100644 java/ql/test/query-tests/ConfusingOverloading/ConfusingOverloading.expected create mode 100644 java/ql/test/query-tests/ConfusingOverloading/ConfusingOverloading.qlref create mode 100644 java/ql/test/query-tests/ConfusingOverloading/TestConfusingOverloading.java create mode 100644 java/ql/test/query-tests/ConstantExpAppearsNonConstant/ConstantExpAppearsNonConstant.expected create mode 100644 java/ql/test/query-tests/ConstantExpAppearsNonConstant/ConstantExpAppearsNonConstant.qlref create mode 100644 java/ql/test/query-tests/ConstantExpAppearsNonConstant/Test.java create mode 100644 java/ql/test/query-tests/ConstantLoopCondition/A.java create mode 100644 java/ql/test/query-tests/ConstantLoopCondition/ConstantLoopCondition.expected create mode 100644 java/ql/test/query-tests/ConstantLoopCondition/ConstantLoopCondition.qlref create mode 100644 java/ql/test/query-tests/ContainerSizeCmpZero/ContainerSizeCmpZero.expected create mode 100644 java/ql/test/query-tests/ContainerSizeCmpZero/ContainerSizeCmpZero.qlref create mode 100644 java/ql/test/query-tests/ContainerSizeCmpZero/Main.java create mode 100644 java/ql/test/query-tests/ContradictoryTypeChecks/ContradictoryTypeChecks.expected create mode 100644 java/ql/test/query-tests/ContradictoryTypeChecks/ContradictoryTypeChecks.qlref create mode 100644 java/ql/test/query-tests/ContradictoryTypeChecks/Test.java create mode 100644 java/ql/test/query-tests/DeadCode/NonAssignedFields/NonAssignedFields.expected create mode 100644 java/ql/test/query-tests/DeadCode/NonAssignedFields/NonAssignedFields.qlref create mode 100644 java/ql/test/query-tests/DeadCode/NonAssignedFields/NonAssignedFieldsTest.java create mode 100644 java/ql/test/query-tests/DeadCode/NonAssignedFields/Test.java create mode 100644 java/ql/test/query-tests/Declarations/BreakInSwitchCase.expected create mode 100644 java/ql/test/query-tests/Declarations/BreakInSwitchCase.qlref create mode 100644 java/ql/test/query-tests/Declarations/Test.java create mode 100644 java/ql/test/query-tests/DefineEqualsWhenAddingFields/A.java create mode 100644 java/ql/test/query-tests/DefineEqualsWhenAddingFields/B.java create mode 100644 java/ql/test/query-tests/DefineEqualsWhenAddingFields/DefineEqualsWhenAddingFields.expected create mode 100644 java/ql/test/query-tests/DefineEqualsWhenAddingFields/DefineEqualsWhenAddingFields.qlref create mode 100644 java/ql/test/query-tests/DefineEqualsWhenAddingFields/DelegateEq.java create mode 100644 java/ql/test/query-tests/DefineEqualsWhenAddingFields/Fragment.java create mode 100644 java/ql/test/query-tests/DefineEqualsWhenAddingFields/I.java create mode 100644 java/ql/test/query-tests/DefineEqualsWhenAddingFields/MyFragment.java create mode 100644 java/ql/test/query-tests/DefineEqualsWhenAddingFields/RefEq.java create mode 100644 java/ql/test/query-tests/EmptyInterface/EmptyInterface.expected create mode 100644 java/ql/test/query-tests/EmptyInterface/EmptyInterface.qlref create mode 100644 java/ql/test/query-tests/EmptyInterface/IAmAlsoOK.java create mode 100644 java/ql/test/query-tests/EmptyInterface/IAmBad.java create mode 100644 java/ql/test/query-tests/EmptyInterface/IAmBadAsWell.java create mode 100644 java/ql/test/query-tests/EmptyInterface/IAmBadToo.java create mode 100644 java/ql/test/query-tests/EmptyInterface/IAmNonEmpty.java create mode 100644 java/ql/test/query-tests/EmptyInterface/IAmNonEmptyAsWell.java create mode 100644 java/ql/test/query-tests/EmptyInterface/IAmNonEmptyToo.java create mode 100644 java/ql/test/query-tests/EmptyInterface/IAmOK.java create mode 100644 java/ql/test/query-tests/EmptyInterface/IAmUseful.java create mode 100644 java/ql/test/query-tests/EqualsArray/EqualsArray.expected create mode 100644 java/ql/test/query-tests/EqualsArray/EqualsArray.qlref create mode 100644 java/ql/test/query-tests/EqualsArray/Test.java create mode 100644 java/ql/test/query-tests/EqualsUsesInstanceOf/EqualsUsesInstanceOf.expected create mode 100644 java/ql/test/query-tests/EqualsUsesInstanceOf/EqualsUsesInstanceOf.qlref create mode 100644 java/ql/test/query-tests/EqualsUsesInstanceOf/Test.java create mode 100644 java/ql/test/query-tests/Finally/Finally.java create mode 100644 java/ql/test/query-tests/Finally/FinallyMayNotComplete.expected create mode 100644 java/ql/test/query-tests/Finally/FinallyMayNotComplete.qlref create mode 100644 java/ql/test/query-tests/HashedButNoHash/HashedButNoHash.expected create mode 100644 java/ql/test/query-tests/HashedButNoHash/HashedButNoHash.qlref create mode 100644 java/ql/test/query-tests/HashedButNoHash/Test.java create mode 100644 java/ql/test/query-tests/IgnoreExceptionalReturn/IgnoreExceptionalReturn.expected create mode 100644 java/ql/test/query-tests/IgnoreExceptionalReturn/IgnoreExceptionalReturn.qlref create mode 100644 java/ql/test/query-tests/IgnoreExceptionalReturn/Test.java create mode 100644 java/ql/test/query-tests/ImpossibleCast/ImpossibleCast.expected create mode 100644 java/ql/test/query-tests/ImpossibleCast/ImpossibleCast.qlref create mode 100644 java/ql/test/query-tests/ImpossibleCast/impossible_cast/A.java create mode 100644 java/ql/test/query-tests/InconsistentEqualsHashCode/InconsistentEqualsHashCode.expected create mode 100644 java/ql/test/query-tests/InconsistentEqualsHashCode/InconsistentEqualsHashCode.qlref create mode 100644 java/ql/test/query-tests/InconsistentEqualsHashCode/Test.java create mode 100644 java/ql/test/query-tests/InconsistentOperations/InconsistentCallOnResult.expected create mode 100644 java/ql/test/query-tests/InconsistentOperations/InconsistentCallOnResult.qlref create mode 100644 java/ql/test/query-tests/InconsistentOperations/Operations.java create mode 100644 java/ql/test/query-tests/InconsistentOperations/ReturnValueIgnored.expected create mode 100644 java/ql/test/query-tests/InconsistentOperations/ReturnValueIgnored.qlref create mode 100644 java/ql/test/query-tests/InconsistentOperations/Test2.java create mode 100644 java/ql/test/query-tests/InconsistentOperations/Test3.java create mode 100644 java/ql/test/query-tests/InconsistentOperations/Test4.java create mode 100644 java/ql/test/query-tests/InconsistentOperations/Test5.java create mode 100644 java/ql/test/query-tests/InconsistentOperations/Test6.java create mode 100644 java/ql/test/query-tests/InefficientOutputStream/InefficientOutputStream.expected create mode 100644 java/ql/test/query-tests/InefficientOutputStream/InefficientOutputStream.qlref create mode 100644 java/ql/test/query-tests/InefficientOutputStream/InefficientOutputStreamBad.java create mode 100644 java/ql/test/query-tests/InefficientOutputStream/InefficientOutputStreamGood.java create mode 100644 java/ql/test/query-tests/InnerClassCouldBeStatic/Classes.java create mode 100644 java/ql/test/query-tests/InnerClassCouldBeStatic/InnerClassCouldBeStatic.expected create mode 100644 java/ql/test/query-tests/InnerClassCouldBeStatic/InnerClassCouldBeStatic.qlref create mode 100644 java/ql/test/query-tests/InnerClassCouldBeStatic/Test.java create mode 100644 java/ql/test/query-tests/Iterable/IterableIterator.expected create mode 100644 java/ql/test/query-tests/Iterable/IterableIterator.qlref create mode 100644 java/ql/test/query-tests/Iterable/Test.java create mode 100644 java/ql/test/query-tests/Iterable/WrappedIterator.expected create mode 100644 java/ql/test/query-tests/Iterable/WrappedIterator.qlref create mode 100644 java/ql/test/query-tests/IteratorRemoveMayFail/IteratorRemoveMayFail.expected create mode 100644 java/ql/test/query-tests/IteratorRemoveMayFail/IteratorRemoveMayFail.qlref create mode 100644 java/ql/test/query-tests/IteratorRemoveMayFail/Test.java create mode 100644 java/ql/test/query-tests/Javadoc/ImpossibleJavadocThrows.expected create mode 100644 java/ql/test/query-tests/Javadoc/ImpossibleJavadocThrows.java create mode 100644 java/ql/test/query-tests/Javadoc/ImpossibleJavadocThrows.qlref create mode 100644 java/ql/test/query-tests/Jdk9Compatibility/JdkInternalAccess.expected create mode 100644 java/ql/test/query-tests/Jdk9Compatibility/JdkInternalAccess.qlref create mode 100644 java/ql/test/query-tests/Jdk9Compatibility/UnderscoreIdentifier.expected create mode 100644 java/ql/test/query-tests/Jdk9Compatibility/UnderscoreIdentifier.qlref create mode 100644 java/ql/test/query-tests/Jdk9Compatibility/p/JdkInternalAccess.java create mode 100644 java/ql/test/query-tests/Jdk9Compatibility/p/_/UnderscoreIdentifier.java create mode 100644 java/ql/test/query-tests/LazyInitStaticField/LazyInitStaticField.expected create mode 100644 java/ql/test/query-tests/LazyInitStaticField/LazyInitStaticField.qlref create mode 100644 java/ql/test/query-tests/LazyInitStaticField/LazyInits.java create mode 100644 java/ql/test/query-tests/MissedTernaryOpportunity/MissedTernaryOpportunity.expected create mode 100644 java/ql/test/query-tests/MissedTernaryOpportunity/MissedTernaryOpportunity.qlref create mode 100644 java/ql/test/query-tests/MissedTernaryOpportunity/MissedTernaryOpportunityTest.java create mode 100644 java/ql/test/query-tests/MissingCallToSuperClone/MissingCallToSuperClone.expected create mode 100644 java/ql/test/query-tests/MissingCallToSuperClone/MissingCallToSuperClone.qlref create mode 100644 java/ql/test/query-tests/MissingCallToSuperClone/Test.java create mode 100644 java/ql/test/query-tests/MissingInstanceofInEquals/AlsoGood.java create mode 100644 java/ql/test/query-tests/MissingInstanceofInEquals/Bad.java create mode 100644 java/ql/test/query-tests/MissingInstanceofInEquals/Good.java create mode 100644 java/ql/test/query-tests/MissingInstanceofInEquals/GoodReturn.java create mode 100644 java/ql/test/query-tests/MissingInstanceofInEquals/MissingInstanceofInEquals.expected create mode 100644 java/ql/test/query-tests/MissingInstanceofInEquals/MissingInstanceofInEquals.qlref create mode 100644 java/ql/test/query-tests/MissingOverrideAnnotation/MissingOverrideAnnotation.expected create mode 100644 java/ql/test/query-tests/MissingOverrideAnnotation/MissingOverrideAnnotation.qlref create mode 100644 java/ql/test/query-tests/MissingOverrideAnnotation/Test.java create mode 100644 java/ql/test/query-tests/MissingSpaceTypo/A.java create mode 100644 java/ql/test/query-tests/MissingSpaceTypo/MissingSpaceTypo.expected create mode 100644 java/ql/test/query-tests/MissingSpaceTypo/MissingSpaceTypo.qlref create mode 100644 java/ql/test/query-tests/MutualDependency/MutualDependency.expected create mode 100644 java/ql/test/query-tests/MutualDependency/MutualDependency.qlref create mode 100644 java/ql/test/query-tests/MutualDependency/onepackage/MutualDependency.java create mode 100644 java/ql/test/query-tests/MutualDependency/otherpackage/OtherClass.java create mode 100644 java/ql/test/query-tests/Naming/ConfusingOverloading.expected create mode 100644 java/ql/test/query-tests/Naming/ConfusingOverloading.qlref create mode 100644 java/ql/test/query-tests/Naming/NamingTest.java create mode 100644 java/ql/test/query-tests/NonPrivateField/NonPrivateField.expected create mode 100644 java/ql/test/query-tests/NonPrivateField/NonPrivateField.qlref create mode 100644 java/ql/test/query-tests/NonPrivateField/NonPrivateFieldTest.java create mode 100644 java/ql/test/query-tests/NonSerializableField/NonSerializableField.expected create mode 100644 java/ql/test/query-tests/NonSerializableField/NonSerializableField.qlref create mode 100644 java/ql/test/query-tests/NonSerializableField/NonSerializableFieldTest.java create mode 100644 java/ql/test/query-tests/NonSerializableInnerClass/NonSerializableInnerClass.expected create mode 100644 java/ql/test/query-tests/NonSerializableInnerClass/NonSerializableInnerClass.qlref create mode 100644 java/ql/test/query-tests/NonSerializableInnerClass/NonSerializableInnerClassTest.java create mode 100644 java/ql/test/query-tests/NonSynchronizedOverride/NonSynchronizedOverride.expected create mode 100644 java/ql/test/query-tests/NonSynchronizedOverride/NonSynchronizedOverride.qlref create mode 100644 java/ql/test/query-tests/NonSynchronizedOverride/Test.java create mode 100644 java/ql/test/query-tests/NotifyWithoutSynch/NotifyWithoutSynch.expected create mode 100644 java/ql/test/query-tests/NotifyWithoutSynch/NotifyWithoutSynch.qlref create mode 100644 java/ql/test/query-tests/NotifyWithoutSynch/Test.java create mode 100644 java/ql/test/query-tests/Nullness/A.java create mode 100644 java/ql/test/query-tests/Nullness/B.java create mode 100644 java/ql/test/query-tests/Nullness/C.java create mode 100644 java/ql/test/query-tests/Nullness/D.java create mode 100644 java/ql/test/query-tests/Nullness/E.java create mode 100644 java/ql/test/query-tests/Nullness/ExprDeref.java create mode 100644 java/ql/test/query-tests/Nullness/F.java create mode 100644 java/ql/test/query-tests/Nullness/NullAlways.expected create mode 100644 java/ql/test/query-tests/Nullness/NullAlways.qlref create mode 100644 java/ql/test/query-tests/Nullness/NullExprDeref.expected create mode 100644 java/ql/test/query-tests/Nullness/NullExprDeref.qlref create mode 100644 java/ql/test/query-tests/Nullness/NullMaybe.expected create mode 100644 java/ql/test/query-tests/Nullness/NullMaybe.qlref create mode 100644 java/ql/test/query-tests/Nullness/options create mode 100644 java/ql/test/query-tests/PartiallyMaskedCatch/PartiallyMaskedCatch.expected create mode 100644 java/ql/test/query-tests/PartiallyMaskedCatch/PartiallyMaskedCatch.qlref create mode 100644 java/ql/test/query-tests/PartiallyMaskedCatch/PartiallyMaskedCatchTest.java create mode 100644 java/ql/test/query-tests/PointlessForwardingMethod/PointlessForwardingMethod.expected create mode 100644 java/ql/test/query-tests/PointlessForwardingMethod/PointlessForwardingMethod.qlref create mode 100644 java/ql/test/query-tests/PointlessForwardingMethod/pointlessforwardingmethod/Test.java create mode 100644 java/ql/test/query-tests/PrintLnArray/PrintLn.expected create mode 100644 java/ql/test/query-tests/PrintLnArray/PrintLn.qlref create mode 100644 java/ql/test/query-tests/PrintLnArray/Test.java create mode 100644 java/ql/test/query-tests/ReadOnlyContainer/ReadOnlyContainer.expected create mode 100644 java/ql/test/query-tests/ReadOnlyContainer/ReadOnlyContainer.qlref create mode 100644 java/ql/test/query-tests/ReadOnlyContainer/Test.java create mode 100644 java/ql/test/query-tests/ReturnValueIgnored/ReturnValueIgnored.expected create mode 100644 java/ql/test/query-tests/ReturnValueIgnored/ReturnValueIgnored.qlref create mode 100644 java/ql/test/query-tests/ReturnValueIgnored/return_value_ignored/Test.java create mode 100644 java/ql/test/query-tests/SelfAssignment/SelfAssignment.expected create mode 100644 java/ql/test/query-tests/SelfAssignment/SelfAssignment.qlref create mode 100644 java/ql/test/query-tests/SelfAssignment/Test.java create mode 100644 java/ql/test/query-tests/SimplifyBoolExpr/SimplifyBoolExpr.expected create mode 100644 java/ql/test/query-tests/SimplifyBoolExpr/SimplifyBoolExpr.java create mode 100644 java/ql/test/query-tests/SimplifyBoolExpr/SimplifyBoolExpr.qlref create mode 100644 java/ql/test/query-tests/SpuriousJavadocParam/Test.java create mode 100644 java/ql/test/query-tests/SpuriousJavadocParam/test.expected create mode 100644 java/ql/test/query-tests/SpuriousJavadocParam/test.qlref create mode 100644 java/ql/test/query-tests/StartInConstructor/StartInConstructor.expected create mode 100644 java/ql/test/query-tests/StartInConstructor/StartInConstructor.qlref create mode 100644 java/ql/test/query-tests/StartInConstructor/Test.java create mode 100644 java/ql/test/query-tests/StaticArray/StaticArray.expected create mode 100644 java/ql/test/query-tests/StaticArray/StaticArray.java create mode 100644 java/ql/test/query-tests/StaticArray/StaticArray.qlref create mode 100644 java/ql/test/query-tests/StringComparison/StringComparison.expected create mode 100644 java/ql/test/query-tests/StringComparison/StringComparison.java create mode 100644 java/ql/test/query-tests/StringComparison/StringComparison.qlref create mode 100644 java/ql/test/query-tests/StringFormat/A.java create mode 100644 java/ql/test/query-tests/StringFormat/MissingFormatArg.expected create mode 100644 java/ql/test/query-tests/StringFormat/MissingFormatArg.qlref create mode 100644 java/ql/test/query-tests/StringFormat/UnusedFormatArg.expected create mode 100644 java/ql/test/query-tests/StringFormat/UnusedFormatArg.qlref create mode 100644 java/ql/test/query-tests/SynchSetUnsynchGet/SynchSetUnsynchSet.expected create mode 100644 java/ql/test/query-tests/SynchSetUnsynchGet/SynchSetUnsynchSet.qlref create mode 100644 java/ql/test/query-tests/SynchSetUnsynchGet/Test.java create mode 100644 java/ql/test/query-tests/TypeMismatch/IncomparableEquals.expected create mode 100644 java/ql/test/query-tests/TypeMismatch/IncomparableEquals.qlref create mode 100644 java/ql/test/query-tests/TypeMismatch/RemoveTypeMismatch.expected create mode 100644 java/ql/test/query-tests/TypeMismatch/RemoveTypeMismatch.qlref create mode 100644 java/ql/test/query-tests/TypeMismatch/incomparable_equals/A.java create mode 100644 java/ql/test/query-tests/TypeMismatch/incomparable_equals/B.java create mode 100644 java/ql/test/query-tests/TypeMismatch/incomparable_equals/C.java create mode 100644 java/ql/test/query-tests/TypeMismatch/incomparable_equals/D.java create mode 100644 java/ql/test/query-tests/TypeMismatch/incomparable_equals/E.java create mode 100644 java/ql/test/query-tests/TypeMismatch/incomparable_equals/F.java create mode 100644 java/ql/test/query-tests/TypeMismatch/incomparable_equals/MyEntry.java create mode 100644 java/ql/test/query-tests/TypeMismatch/incomparable_equals/Test.java create mode 100644 java/ql/test/query-tests/TypeMismatch/remove_type_mismatch/A.java create mode 100644 java/ql/test/query-tests/TypeMismatch/remove_type_mismatch/B.java create mode 100644 java/ql/test/query-tests/UnreadLocal/A.java create mode 100644 java/ql/test/query-tests/UnreadLocal/DeadStoreOfLocal.expected create mode 100644 java/ql/test/query-tests/UnreadLocal/DeadStoreOfLocal.qlref create mode 100644 java/ql/test/query-tests/UnreadLocal/DeadStoreOfLocalUnread.expected create mode 100644 java/ql/test/query-tests/UnreadLocal/DeadStoreOfLocalUnread.qlref create mode 100644 java/ql/test/query-tests/UnreadLocal/UnreadLocal.expected create mode 100644 java/ql/test/query-tests/UnreadLocal/UnreadLocal.qlref create mode 100644 java/ql/test/query-tests/UnreadLocal/UnreadLocal/ImplicitReads.java create mode 100644 java/ql/test/query-tests/UnreadLocal/UnreadLocal/UnreadLocals.java create mode 100644 java/ql/test/query-tests/UnreleasedLock/UnreleasedLock.expected create mode 100644 java/ql/test/query-tests/UnreleasedLock/UnreleasedLock.java create mode 100644 java/ql/test/query-tests/UnreleasedLock/UnreleasedLock.qlref create mode 100644 java/ql/test/query-tests/UnusedField/SomeFields.java create mode 100644 java/ql/test/query-tests/UnusedField/SomeFieldsInSerializable.java create mode 100644 java/ql/test/query-tests/UnusedField/UnusedField.expected create mode 100644 java/ql/test/query-tests/UnusedField/UnusedField.qlref create mode 100644 java/ql/test/query-tests/UseBraces/UseBraces.expected create mode 100644 java/ql/test/query-tests/UseBraces/UseBraces.java create mode 100644 java/ql/test/query-tests/UseBraces/UseBraces.qlref create mode 100644 java/ql/test/query-tests/UselessComparisonTest/A.java create mode 100644 java/ql/test/query-tests/UselessComparisonTest/Test.java create mode 100644 java/ql/test/query-tests/UselessComparisonTest/UselessComparisonTest.expected create mode 100644 java/ql/test/query-tests/UselessComparisonTest/UselessComparisonTest.qlref create mode 100644 java/ql/test/query-tests/UselessNullCheck/A.java create mode 100644 java/ql/test/query-tests/UselessNullCheck/UselessNullCheck.expected create mode 100644 java/ql/test/query-tests/UselessNullCheck/UselessNullCheck.qlref create mode 100644 java/ql/test/query-tests/UselessUpcast/A.java create mode 100644 java/ql/test/query-tests/UselessUpcast/Test.java create mode 100644 java/ql/test/query-tests/UselessUpcast/Test2.java create mode 100644 java/ql/test/query-tests/UselessUpcast/UselessUpcast.expected create mode 100644 java/ql/test/query-tests/UselessUpcast/UselessUpcast.qlref create mode 100644 java/ql/test/query-tests/WhitespaceContradictsPrecedence/WhitespaceContradictsPrecedence.expected create mode 100644 java/ql/test/query-tests/WhitespaceContradictsPrecedence/WhitespaceContradictsPrecedence.java create mode 100644 java/ql/test/query-tests/WhitespaceContradictsPrecedence/WhitespaceContradictsPrecedence.qlref create mode 100644 java/ql/test/query-tests/WriteOnlyContainer/CollectionTest.java create mode 100644 java/ql/test/query-tests/WriteOnlyContainer/MapTest.java create mode 100644 java/ql/test/query-tests/WriteOnlyContainer/WriteOnlyContainer.expected create mode 100644 java/ql/test/query-tests/WriteOnlyContainer/WriteOnlyContainer.qlref create mode 100644 java/ql/test/query-tests/WrongNanComparison/Test.java create mode 100644 java/ql/test/query-tests/WrongNanComparison/WrongNanComparison.expected create mode 100644 java/ql/test/query-tests/WrongNanComparison/WrongNanComparison.qlref create mode 100644 java/ql/test/query-tests/dead-code/DeadCallable/DeadCallable.expected create mode 100644 java/ql/test/query-tests/dead-code/DeadCallable/DeadCallable.qlref create mode 100644 java/ql/test/query-tests/dead-code/DeadCallable/Main.java create mode 100644 java/ql/test/query-tests/dead-code/DeadClass/AnnotationTest.java create mode 100644 java/ql/test/query-tests/dead-code/DeadClass/DeadClass.expected create mode 100644 java/ql/test/query-tests/dead-code/DeadClass/DeadClass.qlref create mode 100644 java/ql/test/query-tests/dead-code/DeadClass/DeadEnumTest.java create mode 100644 java/ql/test/query-tests/dead-code/DeadClass/ExternalDeadCodeCycle.java create mode 100644 java/ql/test/query-tests/dead-code/DeadClass/ExternalDeadRoot.java create mode 100644 java/ql/test/query-tests/dead-code/DeadClass/InternalDeadCodeCycle.java create mode 100644 java/ql/test/query-tests/dead-code/DeadClass/NamespaceTest.java create mode 100644 java/ql/test/query-tests/dead-code/DeadEnumConstant/DeadEnumConstant.expected create mode 100644 java/ql/test/query-tests/dead-code/DeadEnumConstant/DeadEnumConstant.qlref create mode 100644 java/ql/test/query-tests/dead-code/DeadEnumConstant/DeadEnumConstantTest.java create mode 100644 java/ql/test/query-tests/dead-code/DeadField/AnnotationValueTest.java create mode 100644 java/ql/test/query-tests/dead-code/DeadField/AnnotationValueUtil.java create mode 100644 java/ql/test/query-tests/dead-code/DeadField/BasicTest.java create mode 100644 java/ql/test/query-tests/dead-code/DeadField/DeadField.expected create mode 100644 java/ql/test/query-tests/dead-code/DeadField/DeadField.qlref create mode 100644 java/ql/test/query-tests/dead-code/DeadField/ReflectionTest.java create mode 100644 java/ql/test/query-tests/dead-code/DeadMethod/ArbitraryXMLLiveness.java create mode 100644 java/ql/test/query-tests/dead-code/DeadMethod/DeadMethod.expected create mode 100644 java/ql/test/query-tests/dead-code/DeadMethod/DeadMethod.qlref create mode 100644 java/ql/test/query-tests/dead-code/DeadMethod/DefaultConstructorTest.java create mode 100644 java/ql/test/query-tests/dead-code/DeadMethod/InternalDeadCodeCycle.java create mode 100644 java/ql/test/query-tests/dead-code/DeadMethod/JMXTest.java create mode 100644 java/ql/test/query-tests/dead-code/DeadMethod/JaxbTest.java create mode 100644 java/ql/test/query-tests/dead-code/DeadMethod/ReflectionMethodTest.java create mode 100644 java/ql/test/query-tests/dead-code/DeadMethod/ReflectionTest.java create mode 100644 java/ql/test/query-tests/dead-code/DeadMethod/SuppressedConstructorTest.java create mode 100644 java/ql/test/query-tests/dead-code/DeadMethod/arbitrary.xml create mode 100644 java/ql/test/query-tests/dead-code/UselessParameter/Test.java create mode 100644 java/ql/test/query-tests/dead-code/UselessParameter/UselessParameter.expected create mode 100644 java/ql/test/query-tests/dead-code/UselessParameter/UselessParameter.qlref create mode 100644 java/ql/test/query-tests/definitions/Test.java create mode 100644 java/ql/test/query-tests/definitions/definitions.expected create mode 100644 java/ql/test/query-tests/definitions/definitions.qlref create mode 100644 java/ql/test/query-tests/lgtm-example-queries/Test.java create mode 100644 java/ql/test/query-tests/lgtm-example-queries/arrayaccess.expected create mode 100644 java/ql/test/query-tests/lgtm-example-queries/arrayaccess.ql create mode 100644 java/ql/test/query-tests/lgtm-example-queries/returnstatement.expected create mode 100644 java/ql/test/query-tests/lgtm-example-queries/returnstatement.ql create mode 100644 java/ql/test/query-tests/lgtm-example-queries/voidreturntype.expected create mode 100644 java/ql/test/query-tests/lgtm-example-queries/voidreturntype.ql create mode 100644 java/ql/test/query-tests/maven-dependencies/.m2/repository/readme.txt create mode 100644 java/ql/test/query-tests/maven-dependencies/.m2/repository/semmle-test/semmle-test/1.0/pom.xml create mode 100644 java/ql/test/query-tests/maven-dependencies/.m2/repository/semmle-test/semmle-test/1.0/semmle-test-1.0.jar create mode 100644 java/ql/test/query-tests/maven-dependencies/DependencyVersions.expected create mode 100644 java/ql/test/query-tests/maven-dependencies/DependencyVersions.ql create mode 100644 java/ql/test/query-tests/maven-dependencies/MavenDeps.expected create mode 100644 java/ql/test/query-tests/maven-dependencies/MavenDeps.ql create mode 100644 java/ql/test/query-tests/maven-dependencies/MavenPoms.expected create mode 100644 java/ql/test/query-tests/maven-dependencies/MavenPoms.ql create mode 100644 java/ql/test/query-tests/maven-dependencies/UnusedMavenDependencyBinary.expected create mode 100644 java/ql/test/query-tests/maven-dependencies/UnusedMavenDependencyBinary.qlref create mode 100644 java/ql/test/query-tests/maven-dependencies/UnusedMavenDependencySource.expected create mode 100644 java/ql/test/query-tests/maven-dependencies/UnusedMavenDependencySource.qlref create mode 100644 java/ql/test/query-tests/maven-dependencies/another-project/pom.xml create mode 100644 java/ql/test/query-tests/maven-dependencies/another-project/src/main/java/Library.java create mode 100644 java/ql/test/query-tests/maven-dependencies/my-project/pom.xml create mode 100644 java/ql/test/query-tests/maven-dependencies/my-project/src/main/java/Test.java create mode 100644 java/ql/test/query-tests/maven-dependencies/pom.xml create mode 100644 java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.expected create mode 100644 java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.qlref create mode 100644 java/ql/test/query-tests/security/CWE-022/semmle/tests/Test.java create mode 100644 java/ql/test/query-tests/security/CWE-079/semmle/tests/XSS.expected create mode 100644 java/ql/test/query-tests/security/CWE-079/semmle/tests/XSS.java create mode 100644 java/ql/test/query-tests/security/CWE-079/semmle/tests/XSS.qlref create mode 100644 java/ql/test/query-tests/security/CWE-079/semmle/tests/options create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.expected create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.qlref create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlUnescaped.expected create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlUnescaped.qlref create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/Test.java create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/ValidatedVariable.expected create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/ValidatedVariable.ql create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/Validation.java create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/controlledString.expected create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/controlledString.ql create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/endsInQuote.expected create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/endsInQuote.ql create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/getAnAppend.expected create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/getAnAppend.ql create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/getToStringCall.expected create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/getToStringCall.ql create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/sbQuery.expected create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/sbQuery.ql create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/taintedString.expected create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/taintedString.ql create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/validationMethod.expected create mode 100644 java/ql/test/query-tests/security/CWE-089/semmle/examples/validationMethod.ql create mode 100644 java/ql/test/query-tests/security/CWE-113/semmle/tests/ResponseSplitting.expected create mode 100644 java/ql/test/query-tests/security/CWE-113/semmle/tests/ResponseSplitting.java create mode 100644 java/ql/test/query-tests/security/CWE-113/semmle/tests/ResponseSplitting.qlref create mode 100644 java/ql/test/query-tests/security/CWE-113/semmle/tests/options create mode 100644 java/ql/test/query-tests/security/CWE-129/semmle/tests/ImproperValidationOfArrayConstructionCodeSpecified.expected create mode 100644 java/ql/test/query-tests/security/CWE-129/semmle/tests/ImproperValidationOfArrayConstructionCodeSpecified.qlref create mode 100644 java/ql/test/query-tests/security/CWE-129/semmle/tests/ImproperValidationOfArrayConstructionLocal.expected create mode 100644 java/ql/test/query-tests/security/CWE-129/semmle/tests/ImproperValidationOfArrayConstructionLocal.qlref create mode 100644 java/ql/test/query-tests/security/CWE-129/semmle/tests/ImproperValidationOfArrayIndexCodeSpecified.expected create mode 100644 java/ql/test/query-tests/security/CWE-129/semmle/tests/ImproperValidationOfArrayIndexCodeSpecified.qlref create mode 100644 java/ql/test/query-tests/security/CWE-129/semmle/tests/ImproperValidationOfArrayIndexLocal.expected create mode 100644 java/ql/test/query-tests/security/CWE-129/semmle/tests/ImproperValidationOfArrayIndexLocal.qlref create mode 100644 java/ql/test/query-tests/security/CWE-129/semmle/tests/Test.java create mode 100644 java/ql/test/query-tests/security/CWE-134/semmle/tests/ExternallyControlledFormatString.expected create mode 100644 java/ql/test/query-tests/security/CWE-134/semmle/tests/ExternallyControlledFormatString.qlref create mode 100644 java/ql/test/query-tests/security/CWE-134/semmle/tests/ExternallyControlledFormatStringLocal.expected create mode 100644 java/ql/test/query-tests/security/CWE-134/semmle/tests/ExternallyControlledFormatStringLocal.qlref create mode 100644 java/ql/test/query-tests/security/CWE-134/semmle/tests/Test.java create mode 100644 java/ql/test/query-tests/security/CWE-134/semmle/tests/options create mode 100644 java/ql/test/query-tests/security/CWE-190/semmle/tests/ArithmeticTainted.java create mode 100644 java/ql/test/query-tests/security/CWE-190/semmle/tests/ArithmeticTaintedLocal.expected create mode 100644 java/ql/test/query-tests/security/CWE-190/semmle/tests/ArithmeticTaintedLocal.qlref create mode 100644 java/ql/test/query-tests/security/CWE-190/semmle/tests/ArithmeticUncontrolled.expected create mode 100644 java/ql/test/query-tests/security/CWE-190/semmle/tests/ArithmeticUncontrolled.qlref create mode 100644 java/ql/test/query-tests/security/CWE-190/semmle/tests/ArithmeticWithExtremeValues.expected create mode 100644 java/ql/test/query-tests/security/CWE-190/semmle/tests/ArithmeticWithExtremeValues.qlref create mode 100644 java/ql/test/query-tests/security/CWE-190/semmle/tests/Holder.java create mode 100644 java/ql/test/query-tests/security/CWE-190/semmle/tests/InformationLoss.expected create mode 100644 java/ql/test/query-tests/security/CWE-190/semmle/tests/InformationLoss.qlref create mode 100644 java/ql/test/query-tests/security/CWE-190/semmle/tests/IntMultToLong.expected create mode 100644 java/ql/test/query-tests/security/CWE-190/semmle/tests/IntMultToLong.qlref create mode 100644 java/ql/test/query-tests/security/CWE-190/semmle/tests/Test.java create mode 100644 java/ql/test/query-tests/security/CWE-209/semmle/tests/StackTraceExposure.expected create mode 100644 java/ql/test/query-tests/security/CWE-209/semmle/tests/StackTraceExposure.qlref create mode 100644 java/ql/test/query-tests/security/CWE-209/semmle/tests/Test.java create mode 100644 java/ql/test/query-tests/security/CWE-209/semmle/tests/options create mode 100644 java/ql/test/query-tests/security/CWE-311/CWE-312/semmle/tests/CleartextStorageClass.expected create mode 100644 java/ql/test/query-tests/security/CWE-311/CWE-312/semmle/tests/CleartextStorageClass.qlref create mode 100644 java/ql/test/query-tests/security/CWE-311/CWE-312/semmle/tests/CleartextStorageCookie.expected create mode 100644 java/ql/test/query-tests/security/CWE-311/CWE-312/semmle/tests/CleartextStorageCookie.qlref create mode 100644 java/ql/test/query-tests/security/CWE-311/CWE-312/semmle/tests/CleartextStorageProperties.expected create mode 100644 java/ql/test/query-tests/security/CWE-311/CWE-312/semmle/tests/CleartextStorageProperties.qlref create mode 100644 java/ql/test/query-tests/security/CWE-311/CWE-312/semmle/tests/Test.java create mode 100644 java/ql/test/query-tests/security/CWE-311/CWE-312/semmle/tests/options create mode 100644 java/ql/test/query-tests/security/CWE-311/CWE-319/semmle/tests/HttpsUrls.expected create mode 100644 java/ql/test/query-tests/security/CWE-311/CWE-319/semmle/tests/HttpsUrls.qlref create mode 100644 java/ql/test/query-tests/security/CWE-311/CWE-319/semmle/tests/Test.java create mode 100644 java/ql/test/query-tests/security/CWE-311/CWE-319/semmle/tests/UseSSLSocketFactories.expected create mode 100644 java/ql/test/query-tests/security/CWE-311/CWE-319/semmle/tests/UseSSLSocketFactories.qlref create mode 100644 java/ql/test/query-tests/security/CWE-311/CWE-614/semmle/tests/InsecureCookie.expected create mode 100644 java/ql/test/query-tests/security/CWE-311/CWE-614/semmle/tests/InsecureCookie.qlref create mode 100644 java/ql/test/query-tests/security/CWE-311/CWE-614/semmle/tests/Test.java create mode 100644 java/ql/test/query-tests/security/CWE-311/CWE-614/semmle/tests/options create mode 100644 java/ql/test/query-tests/security/CWE-327/semmle/tests/BrokenCryptoAlgorithm.expected create mode 100644 java/ql/test/query-tests/security/CWE-327/semmle/tests/BrokenCryptoAlgorithm.qlref create mode 100644 java/ql/test/query-tests/security/CWE-327/semmle/tests/MaybeBrokenCryptoAlgorithm.expected create mode 100644 java/ql/test/query-tests/security/CWE-327/semmle/tests/MaybeBrokenCryptoAlgorithm.qlref create mode 100644 java/ql/test/query-tests/security/CWE-327/semmle/tests/Test.java create mode 100644 java/ql/test/query-tests/security/CWE-335/semmle/tests/PredictableSeed.expected create mode 100644 java/ql/test/query-tests/security/CWE-335/semmle/tests/PredictableSeed.qlref create mode 100644 java/ql/test/query-tests/security/CWE-335/semmle/tests/Test.java create mode 100644 java/ql/test/query-tests/security/CWE-367/semmle/tests/TOCTOURace.expected create mode 100644 java/ql/test/query-tests/security/CWE-367/semmle/tests/TOCTOURace.qlref create mode 100644 java/ql/test/query-tests/security/CWE-367/semmle/tests/Test.java create mode 100644 java/ql/test/query-tests/security/CWE-421/semmle/SocketAuthRace.expected create mode 100644 java/ql/test/query-tests/security/CWE-421/semmle/SocketAuthRace.qlref create mode 100644 java/ql/test/query-tests/security/CWE-421/semmle/Test.java create mode 100644 java/ql/test/query-tests/security/CWE-421/semmle/options create mode 100644 java/ql/test/query-tests/security/CWE-502/A.java create mode 100644 java/ql/test/query-tests/security/CWE-502/TestMessageBodyReader.java create mode 100644 java/ql/test/query-tests/security/CWE-502/UnsafeDeserialization.expected create mode 100644 java/ql/test/query-tests/security/CWE-502/UnsafeDeserialization.qlref create mode 100644 java/ql/test/query-tests/security/CWE-502/options create mode 100644 java/ql/test/query-tests/security/CWE-601/semmle/tests/UrlRedirect.expected create mode 100644 java/ql/test/query-tests/security/CWE-601/semmle/tests/UrlRedirect.java create mode 100644 java/ql/test/query-tests/security/CWE-601/semmle/tests/UrlRedirect.qlref create mode 100644 java/ql/test/query-tests/security/CWE-601/semmle/tests/options create mode 100644 java/ql/test/query-tests/security/CWE-611/DocumentBuilderTests.java create mode 100644 java/ql/test/query-tests/security/CWE-611/SAXBuilderTests.java create mode 100644 java/ql/test/query-tests/security/CWE-611/SAXParserTests.java create mode 100644 java/ql/test/query-tests/security/CWE-611/SAXReaderTests.java create mode 100644 java/ql/test/query-tests/security/CWE-611/SAXSourceTests.java create mode 100644 java/ql/test/query-tests/security/CWE-611/SchemaTests.java create mode 100644 java/ql/test/query-tests/security/CWE-611/SimpleXMLTests.java create mode 100644 java/ql/test/query-tests/security/CWE-611/TransformerTests.java create mode 100644 java/ql/test/query-tests/security/CWE-611/UnmarshallerTests.java create mode 100644 java/ql/test/query-tests/security/CWE-611/XMLReaderTests.java create mode 100644 java/ql/test/query-tests/security/CWE-611/XPathExpressionTests.java create mode 100644 java/ql/test/query-tests/security/CWE-611/XXE.expected create mode 100644 java/ql/test/query-tests/security/CWE-611/XXE.qlref create mode 100644 java/ql/test/query-tests/security/CWE-611/XmlInputFactoryTests.java create mode 100644 java/ql/test/query-tests/security/CWE-611/options create mode 100644 java/ql/test/query-tests/security/CWE-676/semmle/tests/PotentiallyDangerousFunction.expected create mode 100644 java/ql/test/query-tests/security/CWE-676/semmle/tests/PotentiallyDangerousFunction.qlref create mode 100644 java/ql/test/query-tests/security/CWE-676/semmle/tests/Test.java create mode 100644 java/ql/test/query-tests/security/CWE-681/semmle/tests/NumericCastTaintedLocal.expected create mode 100644 java/ql/test/query-tests/security/CWE-681/semmle/tests/NumericCastTaintedLocal.qlref create mode 100644 java/ql/test/query-tests/security/CWE-681/semmle/tests/Test.java create mode 100644 java/ql/test/query-tests/security/CWE-732/semmle/tests/ReadingFromWorldWritableFile.expected create mode 100644 java/ql/test/query-tests/security/CWE-732/semmle/tests/ReadingFromWorldWritableFile.qlref create mode 100644 java/ql/test/query-tests/security/CWE-732/semmle/tests/Test.java create mode 100644 java/ql/test/query-tests/security/CWE-798/semmle/tests/CredentialsTest.java create mode 100644 java/ql/test/query-tests/security/CWE-798/semmle/tests/FileCredentialTest.java create mode 100644 java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedCredentialsApiCall.expected create mode 100644 java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedCredentialsApiCall.qlref create mode 100644 java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedCredentialsComparison.expected create mode 100644 java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedCredentialsComparison.qlref create mode 100644 java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedCredentialsSourceCall.expected create mode 100644 java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedCredentialsSourceCall.qlref create mode 100644 java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedPasswordField.expected create mode 100644 java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedPasswordField.qlref create mode 100644 java/ql/test/query-tests/security/CWE-798/semmle/tests/Test.java create mode 100644 java/ql/test/query-tests/security/CWE-798/semmle/tests/User.java create mode 100644 java/ql/test/query-tests/security/CWE-807/semmle/tests/ConditionalBypass.expected create mode 100644 java/ql/test/query-tests/security/CWE-807/semmle/tests/ConditionalBypass.qlref create mode 100644 java/ql/test/query-tests/security/CWE-807/semmle/tests/TaintedPermissionsCheck.expected create mode 100644 java/ql/test/query-tests/security/CWE-807/semmle/tests/TaintedPermissionsCheck.qlref create mode 100644 java/ql/test/query-tests/security/CWE-807/semmle/tests/Test.java create mode 100644 java/ql/test/query-tests/security/CWE-807/semmle/tests/options create mode 100644 java/ql/test/query-tests/security/CWE-833/semmle/tests/LockOrderInconsistency.expected create mode 100644 java/ql/test/query-tests/security/CWE-833/semmle/tests/LockOrderInconsistency.qlref create mode 100644 java/ql/test/query-tests/security/CWE-833/semmle/tests/MethodAccessLockOrder.java create mode 100644 java/ql/test/query-tests/security/CWE-833/semmle/tests/ReentrantLockOrder.java create mode 100644 java/ql/test/query-tests/security/CWE-833/semmle/tests/SynchronizedStmtLockOrder.java create mode 100644 java/ql/test/query-tests/security/CWE-835/semmle/tests/InfiniteLoop.expected create mode 100644 java/ql/test/query-tests/security/CWE-835/semmle/tests/InfiniteLoop.java create mode 100644 java/ql/test/query-tests/security/CWE-835/semmle/tests/InfiniteLoop.qlref create mode 100644 java/ql/test/stubs/commons-exec-1.3/org/apache/commons/exec/CommandLine.java create mode 100644 java/ql/test/stubs/commons-exec-1.3/org/apache/commons/exec/DefaultExecutor.java create mode 100644 java/ql/test/stubs/dom4j-2.1.1/org/dom4j/Document.java create mode 100644 java/ql/test/stubs/dom4j-2.1.1/org/dom4j/io/SAXReader.java create mode 100644 java/ql/test/stubs/j2objc/com/google/j2objc/security/IosRSASignature.java create mode 100644 java/ql/test/stubs/jdom-1.1.3/org/jdom/Document.java create mode 100644 java/ql/test/stubs/jdom-1.1.3/org/jdom/input/SAXBuilder.java create mode 100644 java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/MediaType.java create mode 100644 java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/MultivaluedMap.java create mode 100644 java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/ext/MessageBodyReader.java create mode 100644 java/ql/test/stubs/junit-4.11/org/junit/Assert.java create mode 100644 java/ql/test/stubs/junit-jupiter-api-5.2.0/LICENSE.md create mode 100644 java/ql/test/stubs/junit-jupiter-api-5.2.0/org/junit/jupiter/api/Test.java create mode 100644 java/ql/test/stubs/kryo-4.0.2/com/esotericsoftware/kryo/Kryo.java create mode 100644 java/ql/test/stubs/kryo-4.0.2/com/esotericsoftware/kryo/Registration.java create mode 100644 java/ql/test/stubs/kryo-4.0.2/com/esotericsoftware/kryo/io/Input.java create mode 100644 java/ql/test/stubs/servlet-api-2.4/javax/servlet/RequestDispatcher.java create mode 100644 java/ql/test/stubs/servlet-api-2.4/javax/servlet/Servlet.java create mode 100644 java/ql/test/stubs/servlet-api-2.4/javax/servlet/ServletConfig.java create mode 100644 java/ql/test/stubs/servlet-api-2.4/javax/servlet/ServletContext.java create mode 100644 java/ql/test/stubs/servlet-api-2.4/javax/servlet/ServletException.java create mode 100644 java/ql/test/stubs/servlet-api-2.4/javax/servlet/ServletInputStream.java create mode 100644 java/ql/test/stubs/servlet-api-2.4/javax/servlet/ServletOutputStream.java create mode 100644 java/ql/test/stubs/servlet-api-2.4/javax/servlet/ServletRequest.java create mode 100644 java/ql/test/stubs/servlet-api-2.4/javax/servlet/ServletResponse.java create mode 100644 java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/Cookie.java create mode 100644 java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/HttpServlet.java create mode 100644 java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/HttpServletRequest.java create mode 100644 java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/HttpServletResponse.java create mode 100644 java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/HttpSession.java create mode 100644 java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/HttpSessionContext.java create mode 100644 java/ql/test/stubs/shiro-core-1.4.0/org/apache/shiro/SecurityUtils.java create mode 100644 java/ql/test/stubs/shiro-core-1.4.0/org/apache/shiro/subject/Subject.java create mode 100644 java/ql/test/stubs/simple-xml-2.7.1/org/simpleframework/xml/Serializer.java create mode 100644 java/ql/test/stubs/simple-xml-2.7.1/org/simpleframework/xml/core/Persister.java create mode 100644 java/ql/test/stubs/simple-xml-2.7.1/org/simpleframework/xml/stream/DocumentProvider.java create mode 100644 java/ql/test/stubs/simple-xml-2.7.1/org/simpleframework/xml/stream/Formatter.java create mode 100644 java/ql/test/stubs/simple-xml-2.7.1/org/simpleframework/xml/stream/NodeBuilder.java create mode 100644 java/ql/test/stubs/simple-xml-2.7.1/org/simpleframework/xml/stream/Provider.java create mode 100644 java/ql/test/stubs/simple-xml-2.7.1/org/simpleframework/xml/stream/StreamProvider.java create mode 100644 java/ql/test/stubs/snakeyaml-1.21/org/yaml/snakeyaml/Yaml.java create mode 100644 java/ql/test/stubs/snakeyaml-1.21/org/yaml/snakeyaml/constructor/BaseConstructor.java create mode 100644 java/ql/test/stubs/snakeyaml-1.21/org/yaml/snakeyaml/constructor/Constructor.java create mode 100644 java/ql/test/stubs/snakeyaml-1.21/org/yaml/snakeyaml/constructor/SafeConstructor.java create mode 100644 java/ql/test/stubs/snakeyaml-1.21/org/yaml/snakeyaml/events/Event.java create mode 100644 java/ql/test/stubs/xstream-1.4.10/com/thoughtworks/xstream/XStream.java diff --git a/java/ql/src/.project b/java/ql/src/.project new file mode 100644 index 00000000000..066710453d3 --- /dev/null +++ b/java/ql/src/.project @@ -0,0 +1,12 @@ + + + semmlecode-queries + + + + + + + com.semmle.plugin.qdt.core.qlnature + + diff --git a/java/ql/src/.qlpath b/java/ql/src/.qlpath new file mode 100644 index 00000000000..ebf2dc76dfe --- /dev/null +++ b/java/ql/src/.qlpath @@ -0,0 +1,5 @@ + + + /semmlecode-queries/config/semmlecode.dbscheme + java + diff --git a/java/ql/src/.settings/org.eclipse.jdt.core.prefs b/java/ql/src/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..860376bf985 --- /dev/null +++ b/java/ql/src/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +#Tue Nov 04 11:42:37 GMT 2008 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.5 diff --git a/java/ql/src/.vs/VSWorkspaceSettings.json b/java/ql/src/.vs/VSWorkspaceSettings.json new file mode 100644 index 00000000000..ed6b06a92e5 --- /dev/null +++ b/java/ql/src/.vs/VSWorkspaceSettings.json @@ -0,0 +1,8 @@ +{ + "ql.projects" : { + "." : { + "dbScheme" : "config/semmlecode.dbscheme", + "libraryPath" : [] + } + } +} \ No newline at end of file diff --git a/java/ql/src/Advisory/Declarations/MissingOverrideAnnotation.java b/java/ql/src/Advisory/Declarations/MissingOverrideAnnotation.java new file mode 100644 index 00000000000..d571aaacacd --- /dev/null +++ b/java/ql/src/Advisory/Declarations/MissingOverrideAnnotation.java @@ -0,0 +1,15 @@ +class Rectangle +{ + private int w = 10, h = 10; + public int getArea() { + return w * h; + } +} + +class Triangle extends Rectangle +{ + @Override // Annotation of an overriding method + public int getArea() { + return super.getArea() / 2; + } +} \ No newline at end of file diff --git a/java/ql/src/Advisory/Declarations/MissingOverrideAnnotation.qhelp b/java/ql/src/Advisory/Declarations/MissingOverrideAnnotation.qhelp new file mode 100644 index 00000000000..2982238f764 --- /dev/null +++ b/java/ql/src/Advisory/Declarations/MissingOverrideAnnotation.qhelp @@ -0,0 +1,54 @@ + + + + + +

+Java enables you to annotate methods that are intended to override a method in a superclass. +Compilers are required to generate an error if such an annotated method does not override a method +in a superclass, which provides increased protection from potential defects. An annotated method also +improves code readability. +

+ +
+ + +

+Add an @Override annotation to a method that is intended to override a method in a +superclass. +

+ +
+ + +

In the following example, Triangle.getArea overrides Rectangle.getArea, +so it is annotated with @Override.

+ + + +
+ + + +
  • + J. Bloch, Effective Java (second edition), Item 36. + Addison-Wesley, 2008. +
  • +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • +
  • + Java Platform, Standard Edition 6, API Specification: + Annotation Type Override. +
  • +
  • + The Java Tutorials: + Predefined Annotation Types. +
  • + + +
    +
    diff --git a/java/ql/src/Advisory/Declarations/MissingOverrideAnnotation.ql b/java/ql/src/Advisory/Declarations/MissingOverrideAnnotation.ql new file mode 100644 index 00000000000..f2df9e25315 --- /dev/null +++ b/java/ql/src/Advisory/Declarations/MissingOverrideAnnotation.ql @@ -0,0 +1,30 @@ +/** + * @name Missing Override annotation + * @description A method that overrides a method in a superclass but does not have an 'Override' + * annotation cannot take advantage of compiler checks, and makes code less readable. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/missing-override-annotation + * @tags maintainability + */ +import java + +class OverridingMethod extends Method { + OverridingMethod() { + exists(Method m | this.overrides(m)) + } + + predicate isOverrideAnnotated() { + this.getAnAnnotation() instanceof OverrideAnnotation + } +} + +from OverridingMethod m, Method overridden +where + m.fromSource() and + m.overrides(overridden) and + not m.isOverrideAnnotated() and + not exists(FunctionalExpr mref | mref.asMethod() = m) +select m, "This method overrides $@; it is advisable to add an Override annotation.", + overridden, overridden.getDeclaringType() + "." + overridden.getName() diff --git a/java/ql/src/Advisory/Declarations/NonFinalImmutableField.qhelp b/java/ql/src/Advisory/Declarations/NonFinalImmutableField.qhelp new file mode 100644 index 00000000000..fe49777ba14 --- /dev/null +++ b/java/ql/src/Advisory/Declarations/NonFinalImmutableField.qhelp @@ -0,0 +1,35 @@ + + + + + +

    A field of immutable type that is not declared final, but is assigned to only in a +constructor or static initializer of its declaring type, may lead to defects and makes code less +readable. This is because other parts of the code may be based on the assumption that the field has +a constant value, and a later modification, which includes an assignment to the field, may +invalidate this assumption. +

    + +
    + + +

    If a field of immutable type is assigned to only during class or instance initialization, +you should usually declare it final. This forces the compiler to verify that the field +value cannot be changed subsequently, which can help to avoid defects and increase code readability. +

    + +
    + + + +
  • + Java Language Specification: + 4.12.4 final Variables, + 8.3.1.2 final Fields. +
  • + + +
    +
    diff --git a/java/ql/src/Advisory/Declarations/NonFinalImmutableField.ql b/java/ql/src/Advisory/Declarations/NonFinalImmutableField.ql new file mode 100644 index 00000000000..b5179abd1ed --- /dev/null +++ b/java/ql/src/Advisory/Declarations/NonFinalImmutableField.ql @@ -0,0 +1,52 @@ +/** + * @name Non-final immutable field + * @description A field of immutable type that is assigned to only in a constructor or static + * initializer of its declaring type, but is not declared 'final', may lead to defects + * and makes code less readable. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/non-final-immutable-field + * @tags reliability + */ +import java + +class Initialization extends Callable { + Initialization() { + this instanceof Constructor or + this instanceof InitializerMethod + } +} + +/** A binary or unary assignment. */ +class AnyAssignment extends Expr { + AnyAssignment() { + this instanceof Assignment or + this instanceof UnaryAssignExpr + } + + /** The expression modified by this assignment. */ + Expr getDest() { + this.(Assignment).getDest() = result or + this.(UnaryAssignExpr).getExpr() = result + } +} + +class ImmutableField extends Field { + ImmutableField() { + this.fromSource() and + not this instanceof EnumConstant and + this.getType() instanceof ImmutableType and + // The field is only assigned to in a constructor or static initializer of the type it is declared in. + forall(FieldAccess fw, AnyAssignment ae | + fw.getField().getSourceDeclaration() = this and + fw = ae.getDest() + | ae.getEnclosingCallable().getDeclaringType() = this.getDeclaringType() and + ae.getEnclosingCallable() instanceof Initialization + ) + } +} + +from ImmutableField f +where not f.isFinal() +select f, "This immutable field is not declared final but is only assigned to during initialization." diff --git a/java/ql/src/Advisory/Declarations/NonPrivateField.qhelp b/java/ql/src/Advisory/Declarations/NonPrivateField.qhelp new file mode 100644 index 00000000000..873f0298f06 --- /dev/null +++ b/java/ql/src/Advisory/Declarations/NonPrivateField.qhelp @@ -0,0 +1,38 @@ + + + + + +

    A non-final or non-static field that is not declared private, +but is not accessed outside of its declaring type, may decrease code maintainability. This is because +a field that is accessible from outside the class that it is declared in tends to restrict the class +to a particular implementation. +

    + +
    + + +

    In the spirit of encapsulation, it is generally advisable to choose the +most restrictive access modifier (private) for a field, unless +there is a good reason to increase its visibility. +

    + +
    + + + +
  • + J. Bloch, Effective Java (second edition), + Item 13. + Addison-Wesley, 2008. +
  • +
  • + The Java Tutorials: + Controlling Access to Members of a Class. +
  • + + +
    +
    diff --git a/java/ql/src/Advisory/Declarations/NonPrivateField.ql b/java/ql/src/Advisory/Declarations/NonPrivateField.ql new file mode 100644 index 00000000000..4526f9fc89c --- /dev/null +++ b/java/ql/src/Advisory/Declarations/NonPrivateField.ql @@ -0,0 +1,28 @@ +/** + * @name Non-private field + * @description A non-constant field that is not declared 'private', + * but is not accessed outside of its declaring type, may decrease code maintainability. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/non-private-field + * @tags maintainability + */ +import java +import semmle.code.java.JDKAnnotations + +class NonConstantSourceField extends Field { + NonConstantSourceField() { + this.fromSource() and + not (this.isFinal() and this.isStatic()) + } +} + +from NonConstantSourceField f +where + not f.isPrivate() and + not exists(VarAccess va | va.getVariable() = f | + va.getEnclosingCallable().getDeclaringType() != f.getDeclaringType() + ) and + not f.getAnAnnotation() instanceof ReflectiveAccessAnnotation +select f, "This non-private field is not accessed outside of its declaring type." diff --git a/java/ql/src/Advisory/Deprecated Code/AvoidDeprecatedCallableAccess.qhelp b/java/ql/src/Advisory/Deprecated Code/AvoidDeprecatedCallableAccess.qhelp new file mode 100644 index 00000000000..951149df737 --- /dev/null +++ b/java/ql/src/Advisory/Deprecated Code/AvoidDeprecatedCallableAccess.qhelp @@ -0,0 +1,47 @@ + + + + + +

    +A method (or constructor) can be marked as deprecated using either the @Deprecated +annotation or the @deprecated Javadoc tag. Using a method that has been +marked as deprecated is bad practice, typically for one or more of the following reasons:

    + +
      +
    • The method is dangerous.
    • +
    • There is a better alternative method.
    • +
    • Methods that are marked as deprecated are often removed from future versions of an API. So using +a deprecated method may cause extra maintenance effort when the API is upgraded.
    • +
    + +
    + + +

    Avoid using a method that has been marked as deprecated. Follow any guidance that +is provided with the @deprecated Javadoc tag, which should explain how to replace the +call to the deprecated method. +

    + +
    + + + +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • +
  • + Java Platform, Standard Edition 6, API Specification: + Annotation Type Deprecated. +
  • +
  • + Java SE Documentation: + How and When To Deprecate APIs. +
  • + + +
    +
    diff --git a/java/ql/src/Advisory/Deprecated Code/AvoidDeprecatedCallableAccess.ql b/java/ql/src/Advisory/Deprecated Code/AvoidDeprecatedCallableAccess.ql new file mode 100644 index 00000000000..6b122338ad1 --- /dev/null +++ b/java/ql/src/Advisory/Deprecated Code/AvoidDeprecatedCallableAccess.ql @@ -0,0 +1,28 @@ +/** + * @name Deprecated method or constructor invocation + * @description Using a method or constructor that has been marked as deprecated may be dangerous or + * fail to take advantage of a better method or constructor. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/deprecated-call + * @tags maintainability + * non-attributable + * external/cwe/cwe-477 + */ +import java + +private +predicate isDeprecatedCallable(Callable c) { + c.getAnAnnotation() instanceof DeprecatedAnnotation or + exists(c.getDoc().getJavadoc().getATag("@deprecated")) +} + +from Call ca, Callable c +where + ca.getCallee() = c and + isDeprecatedCallable(c) and + // Exclude deprecated calls from within deprecated code. + not isDeprecatedCallable(ca.getCaller()) +select ca, "Invoking $@ should be avoided because it has been deprecated.", + c, c.getDeclaringType() + "." + c.getName() diff --git a/java/ql/src/Advisory/Documentation/ImpossibleJavadocThrows.java b/java/ql/src/Advisory/Documentation/ImpossibleJavadocThrows.java new file mode 100644 index 00000000000..5a1c5fea02d --- /dev/null +++ b/java/ql/src/Advisory/Documentation/ImpossibleJavadocThrows.java @@ -0,0 +1,8 @@ +/** + * Javadoc for method. + * + * @throws Exception if a problem occurs. + */ +public void noThrow() { + System.out.println("This method does not throw."); +} \ No newline at end of file diff --git a/java/ql/src/Advisory/Documentation/ImpossibleJavadocThrows.qhelp b/java/ql/src/Advisory/Documentation/ImpossibleJavadocThrows.qhelp new file mode 100644 index 00000000000..0244c3860f6 --- /dev/null +++ b/java/ql/src/Advisory/Documentation/ImpossibleJavadocThrows.qhelp @@ -0,0 +1,46 @@ + + + + + +

    +A Javadoc @throws or @exception tag that references an exception +that cannot be thrown is misleading. +

    + +
    + + +

    +Ensure that you only include the @throws or @exception tags in Javadoc +when an exception can be thrown. +

    + +
    + + +

    The following example shows a method with Javadoc that claims it can throw +Exception. Since Exception is a checked exception and the method +does not declare that it may throw an exception, the Javadoc is wrong and should be updated.

    + + + +

    In the following example the Javadoc has been corrected by removing the @throws +tag.

    + + + +
    + + + +
  • + Java SE Documentation: + How to Write Doc Comments for the Javadoc Tool, +
  • + + +
    +
    diff --git a/java/ql/src/Advisory/Documentation/ImpossibleJavadocThrows.ql b/java/ql/src/Advisory/Documentation/ImpossibleJavadocThrows.ql new file mode 100644 index 00000000000..029303caaed --- /dev/null +++ b/java/ql/src/Advisory/Documentation/ImpossibleJavadocThrows.ql @@ -0,0 +1,34 @@ +/** + * @name Javadoc has impossible 'throws' tag + * @description Javadoc that incorrectly claims a method or constructor can throw an exception + * is misleading. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/inconsistent-javadoc-throws + * @tags maintainability + */ + +import java + +RefType getTaggedType(ThrowsTag tag) { + result.hasName(tag.getExceptionName()) and + exists(ImportType i | i.getFile() = tag.getFile() | i.getImportedType() = result) +} + +predicate canThrow(Callable callable, RefType exception) { + exists(string uncheckedException | + uncheckedException = "RuntimeException" or uncheckedException = "Error" + | + exception.getASupertype*().hasQualifiedName("java.lang", uncheckedException) + ) or + callable.getAnException().getType().getASubtype*() = exception +} + +from ThrowsTag throwsTag, RefType thrownType, Callable docMethod +where + getTaggedType(throwsTag) = thrownType and + docMethod.getDoc().getJavadoc().getAChild*() = throwsTag and + not canThrow(docMethod, thrownType) +select throwsTag, + "Javadoc for " + docMethod + " claims to throw " + thrownType.getName() + " but this is impossible." diff --git a/java/ql/src/Advisory/Documentation/ImpossibleJavadocThrowsFix.java b/java/ql/src/Advisory/Documentation/ImpossibleJavadocThrowsFix.java new file mode 100644 index 00000000000..95026acc8a4 --- /dev/null +++ b/java/ql/src/Advisory/Documentation/ImpossibleJavadocThrowsFix.java @@ -0,0 +1,6 @@ +/** + * Javadoc for method. + */ +public void noThrow() { + System.out.println("This method does not throw."); +} \ No newline at end of file diff --git a/java/ql/src/Advisory/Documentation/JavadocCommon.qll b/java/ql/src/Advisory/Documentation/JavadocCommon.qll new file mode 100644 index 00000000000..228d4f000a9 --- /dev/null +++ b/java/ql/src/Advisory/Documentation/JavadocCommon.qll @@ -0,0 +1,121 @@ +import java + +/** Holds if the given `Javadoc` contains a minimum of a few characters of text. */ +private +predicate acceptableDocText(Javadoc j) { + // Require minimum combined length of all non-tag elements. + sum(JavadocElement e, int toSum | + e = j.getAChild() and + not e = j.getATag(_) and + toSum = e.toString().length() + | + toSum + ) >= 5 +} + +/** Holds if the given `JavadocTag` contains a minimum of a few characters of text. */ +private +predicate acceptableTag(JavadocTag t) { + sum(JavadocElement e, int toSum | + e = t.getAChild() and + toSum = e.toString().length() + | + toSum + ) >= 5 +} + +/** A public `RefType`. */ +class DocuRefType extends RefType { + DocuRefType() { + this.fromSource() and + this.isPublic() + } + + predicate hasAcceptableDocText() { + acceptableDocText(this.getDoc().getJavadoc()) + } +} + +/** A public (non-getter, non-setter) `Callable` that does not override another method. */ +class DocuCallable extends Callable { + DocuCallable() { + this.fromSource() and + this.isPublic() and + // Ignore overriding methods (only require Javadoc on the root method). + not exists(Method root | this.(Method).overrides(root)) and + // Ignore getters and setters. + not this instanceof SetterMethod and + not this instanceof GetterMethod and + // Ignore synthetic/implicit constructors. + not this.getLocation() = this.getDeclaringType().getLocation() + } + + predicate hasAcceptableDocText() { + acceptableDocText(this.getDoc().getJavadoc()) + } + + string toMethodOrConstructorString() { + (this instanceof Method and result = "method") or + (this instanceof Constructor and result = "constructor") + } +} + +/** A `Parameter` belonging to a `DocuCallable` that has some `Javadoc`. */ +class DocuParam extends Parameter { + DocuParam() { + this.fromSource() and + this.getCallable() instanceof DocuCallable and + // Only consider callables with Javadoc. + exists(this.getCallable().getDoc().getJavadoc()) + } + + /** Holds if this parameter has a non-trivial `@param` tag. */ + predicate hasAcceptableParamTag() { + exists(ParamTag t | + t = this.getCallable().getDoc().getJavadoc().getATag("@param") and + t.getParamName() = this.getName() and + acceptableTag(t) + ) + } +} + +/** A `DocuCallable` that is a method with `Javadoc` whose return type is not `void`. */ +class DocuReturn extends DocuCallable { + DocuReturn() { + this instanceof Method and + not this.getReturnType().hasName("void") and + // Only consider methods with Javadoc. + exists(this.getDoc().getJavadoc()) + } + + /** Holds if this callable's `Javadoc` has a non-trivial `@return` tag. */ + predicate hasAcceptableReturnTag() { + acceptableTag(this.getDoc().getJavadoc().getATag("@return")) + } +} + +/** A `DocuCallable` that has `Javadoc` and throws at least one exception. */ +class DocuThrows extends DocuCallable { + DocuThrows() { + this.fromSource() and + // Only consider callables that throw at least one exception. + exists(this.getAnException()) and + // Only consider callables with Javadoc. + exists(this.getDoc().getJavadoc()) + } + + /** + * Holds if this callable has a non-trivial Javadoc `@throws` or `@exception` tag + * documenting the given `Exception e`. + */ + predicate hasAcceptableThrowsTag(Exception e) { + exists(Javadoc j | + j = this.getDoc().getJavadoc() and + exists(JavadocTag t | + (t = j.getATag("@throws") or t = j.getATag("@exception")) and + t.getChild(0).toString() = e.getName() and + acceptableTag(t) + ) + ) + } +} diff --git a/java/ql/src/Advisory/Documentation/MissingJavadocMethods.java b/java/ql/src/Advisory/Documentation/MissingJavadocMethods.java new file mode 100644 index 00000000000..6e05dda16d8 --- /dev/null +++ b/java/ql/src/Advisory/Documentation/MissingJavadocMethods.java @@ -0,0 +1,16 @@ +/** + * Extracts the user's name from the input arguments. + * + * Precondition: 'args' should contain at least one element, the user's name. + * + * @param args the command-line arguments. + * @return the user's name (the first command-line argument). + * @throws NoNameException if 'args' contains no element. + */ +public static String getName(String[] args) throws NoNameException { + if(args.length == 0) { + throw new NoNameException(); + } else { + return args[0]; + } +} \ No newline at end of file diff --git a/java/ql/src/Advisory/Documentation/MissingJavadocMethods.qhelp b/java/ql/src/Advisory/Documentation/MissingJavadocMethods.qhelp new file mode 100644 index 00000000000..3df5a843f8a --- /dev/null +++ b/java/ql/src/Advisory/Documentation/MissingJavadocMethods.qhelp @@ -0,0 +1,64 @@ + + + + + +

    +A public method or constructor that does not have a Javadoc comment makes an API more +difficult to understand and maintain. +

    + +
    + + +

    +Public methods and constructors should be documented to make an API usable. +For the purpose of code maintainability, it is also advisable to document non-public +methods and constructors. +

    +

    +The Javadoc comment should describe what the method or constructor does +rather than how, to allow for any potential implementation change that is +invisible to users of an API. It should include the following:

    +
      +
    • A description of any preconditions or postconditions
    • +
    • Javadoc tag elements that describe any parameters, return value, and thrown exceptions
    • +
    • Any other important aspects such as side-effects and thread safety
    • +
    +

    +Documentation for users of an API should be written using the standard Javadoc format. +This can be accessed conveniently by users of an API from within standard IDEs, +and can be transformed automatically into HTML format. +

    + +
    + + +

    The following example shows a good Javadoc comment, which clearly explains what the method does, +its parameter, return value, and thrown exception.

    + + + +
    + + + +
  • + J. Bloch, Effective Java (second edition), Item 44. + Addison-Wesley, 2008. +
  • +
  • +Help - Eclipse Platform: +Java Compiler Javadoc Preferences. +
  • +
  • + Java SE Documentation: + How to Write Doc Comments for the Javadoc Tool, + Requirements for Writing Java API Specifications. +
  • + + +
    +
    diff --git a/java/ql/src/Advisory/Documentation/MissingJavadocMethods.ql b/java/ql/src/Advisory/Documentation/MissingJavadocMethods.ql new file mode 100644 index 00000000000..e952598c2a6 --- /dev/null +++ b/java/ql/src/Advisory/Documentation/MissingJavadocMethods.ql @@ -0,0 +1,16 @@ +/** + * @name Missing Javadoc for public method or constructor + * @description A public method or constructor that does not have a Javadoc comment affects + * maintainability. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/undocumented-function + * @tags maintainability + */ +import java +import JavadocCommon + +from DocuCallable c +where not c.hasAcceptableDocText() +select c, "This " + c.toMethodOrConstructorString() + " does not have a non-trivial Javadoc comment." diff --git a/java/ql/src/Advisory/Documentation/MissingJavadocParameters.qhelp b/java/ql/src/Advisory/Documentation/MissingJavadocParameters.qhelp new file mode 100644 index 00000000000..e7ddb466a26 --- /dev/null +++ b/java/ql/src/Advisory/Documentation/MissingJavadocParameters.qhelp @@ -0,0 +1,45 @@ + + + + + +

    +A public method or constructor that does not have a Javadoc tag for each parameter +makes an API more difficult to understand and maintain.

    + +
    + + +

    The Javadoc comment for a method or constructor should include a Javadoc tag element that +describes each parameter.

    + +
    + + +

    The following example shows a good Javadoc comment, which clearly explains the method's +parameter.

    + + + +
    + + + +
  • + J. Bloch, Effective Java (second edition), Item 44. + Addison-Wesley, 2008. +
  • +
  • +Help - Eclipse Platform: +Java Compiler Javadoc Preferences. +
  • +
  • + Java SE Documentation: + How to Write Doc Comments for the Javadoc Tool, + Requirements for Writing Java API Specifications. +
  • + +
    +
    diff --git a/java/ql/src/Advisory/Documentation/MissingJavadocParameters.ql b/java/ql/src/Advisory/Documentation/MissingJavadocParameters.ql new file mode 100644 index 00000000000..87712e8d9aa --- /dev/null +++ b/java/ql/src/Advisory/Documentation/MissingJavadocParameters.ql @@ -0,0 +1,16 @@ +/** + * @name Missing Javadoc for parameter + * @description A public method or constructor that does not have a Javadoc tag for each parameter + * affects maintainability. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/undocumented-parameter + * @tags maintainability + */ +import java +import JavadocCommon + +from DocuParam p +where not p.hasAcceptableParamTag() +select p, "This parameter does not have a non-trivial Javadoc tag." diff --git a/java/ql/src/Advisory/Documentation/MissingJavadocReturnValues.qhelp b/java/ql/src/Advisory/Documentation/MissingJavadocReturnValues.qhelp new file mode 100644 index 00000000000..c12e408781f --- /dev/null +++ b/java/ql/src/Advisory/Documentation/MissingJavadocReturnValues.qhelp @@ -0,0 +1,46 @@ + + + + + +

    +A public method that does not have a Javadoc tag for its return value +makes an API more difficult to understand and maintain.

    + +
    + + +

    The Javadoc comment for a method should include a Javadoc tag element that +describes the return value.

    + +
    + + +

    The following example shows a good Javadoc comment, which clearly explains the method's +return value.

    + + + +
    + + + +
  • + J. Bloch, Effective Java (second edition), Item 44. + Addison-Wesley, 2008. +
  • +
  • +Help - Eclipse Platform: +Java Compiler Javadoc Preferences. +
  • +
  • + Java SE Documentation: + How to Write Doc Comments for the Javadoc Tool, + Requirements for Writing Java API Specifications. +
  • + + +
    +
    diff --git a/java/ql/src/Advisory/Documentation/MissingJavadocReturnValues.ql b/java/ql/src/Advisory/Documentation/MissingJavadocReturnValues.ql new file mode 100644 index 00000000000..683f0c96b50 --- /dev/null +++ b/java/ql/src/Advisory/Documentation/MissingJavadocReturnValues.ql @@ -0,0 +1,16 @@ +/** + * @name Missing Javadoc for method return value + * @description A public method that does not have a Javadoc tag for its return + * value affects maintainability. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/undocumented-return-value + * @tags maintainability + */ +import java +import JavadocCommon + +from DocuReturn c +where not c.hasAcceptableReturnTag() +select c, "This method's return value does not have a non-trivial Javadoc tag." diff --git a/java/ql/src/Advisory/Documentation/MissingJavadocThrows.qhelp b/java/ql/src/Advisory/Documentation/MissingJavadocThrows.qhelp new file mode 100644 index 00000000000..f2c9d5b2789 --- /dev/null +++ b/java/ql/src/Advisory/Documentation/MissingJavadocThrows.qhelp @@ -0,0 +1,49 @@ + + + + + +

    +A public method or constructor that throws an exception but +does not have a Javadoc tag for the exception makes an API more difficult to understand and maintain. +This includes checked exceptions in throws clauses and unchecked exceptions that are +explicitly thrown in throw statements. +

    + +
    + + +

    The Javadoc comment for a method or constructor should include a Javadoc tag element that +describes each thrown exception.

    + +
    + + +

    The following example shows a good Javadoc comment, which clearly explains the method's +thrown exception.

    + + + +
    + + + +
  • + J. Bloch, Effective Java (second edition), Items 44 and 62. + Addison-Wesley, 2008. +
  • +
  • +Help - Eclipse Platform: +Java Compiler Javadoc Preferences. +
  • +
  • + Java SE Documentation: + How to Write Doc Comments for the Javadoc Tool, + Requirements for Writing Java API Specifications. +
  • + + +
    +
    diff --git a/java/ql/src/Advisory/Documentation/MissingJavadocThrows.ql b/java/ql/src/Advisory/Documentation/MissingJavadocThrows.ql new file mode 100644 index 00000000000..a6aa1602d00 --- /dev/null +++ b/java/ql/src/Advisory/Documentation/MissingJavadocThrows.ql @@ -0,0 +1,22 @@ +/** + * @name Missing Javadoc for thrown exception + * @description A public method or constructor that throws an exception but does not have a + * Javadoc tag for the exception affects maintainability. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/undocumented-exception + * @tags maintainability + */ +import java +import JavadocCommon + +from DocuThrows c, RefType t +where + exists(Exception e | + c.getAnException() = e and + e.getType() = t and + not c.hasAcceptableThrowsTag(e) + ) +select c, "This " + c.toMethodOrConstructorString() + " throws $@ but does not have a corresponding Javadoc tag.", + t, t.getName() diff --git a/java/ql/src/Advisory/Documentation/MissingJavadocTypes.java b/java/ql/src/Advisory/Documentation/MissingJavadocTypes.java new file mode 100644 index 00000000000..f59cf5dc46a --- /dev/null +++ b/java/ql/src/Advisory/Documentation/MissingJavadocTypes.java @@ -0,0 +1,9 @@ +/** + * The Stack class represents a last-in-first-out stack of objects. + * + * @author Joseph Bergin + * @version 1.0, May 2000 + * Note that this version is not thread safe. + */ +public class Stack { +// ... \ No newline at end of file diff --git a/java/ql/src/Advisory/Documentation/MissingJavadocTypes.qhelp b/java/ql/src/Advisory/Documentation/MissingJavadocTypes.qhelp new file mode 100644 index 00000000000..0999b040645 --- /dev/null +++ b/java/ql/src/Advisory/Documentation/MissingJavadocTypes.qhelp @@ -0,0 +1,54 @@ + + + + + +

    +A public class or interface that does not have a Javadoc comment makes an API more +difficult to understand and maintain. +

    + +
    + + +

    +Public classes and interfaces should be documented to make an API usable. +For the purpose of code maintainability, it is also advisable to document non-public +classes and interfaces. +

    +

    +Documentation for users of an API should be written using the standard Javadoc format. +This can be accessed conveniently by users of an API from within standard IDEs, +and can be transformed automatically into HTML format. +

    + +
    + + +

    The following example shows a good Javadoc comment, which clearly explains what the class does, +its author, and version.

    + + +
    + + + +
  • + J. Bloch, Effective Java (second edition), Item 44. + Addison-Wesley, 2008. +
  • +
  • +Help - Eclipse Platform: +Java Compiler Javadoc Preferences. +
  • +
  • + Java SE Documentation: + How to Write Doc Comments for the Javadoc Tool, + Requirements for Writing Java API Specifications. +
  • + + +
    +
    diff --git a/java/ql/src/Advisory/Documentation/MissingJavadocTypes.ql b/java/ql/src/Advisory/Documentation/MissingJavadocTypes.ql new file mode 100644 index 00000000000..da922aa0be5 --- /dev/null +++ b/java/ql/src/Advisory/Documentation/MissingJavadocTypes.ql @@ -0,0 +1,16 @@ +/** + * @name Missing Javadoc for public type + * @description A public class or interface that does not have a Javadoc comment affects + * maintainability. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/undocumented-type + * @tags maintainability + */ +import java +import JavadocCommon + +from DocuRefType t +where not t.hasAcceptableDocText() +select t, "This type does not have a non-trivial Javadoc comment." diff --git a/java/ql/src/Advisory/Documentation/SpuriousJavadocParam.java b/java/ql/src/Advisory/Documentation/SpuriousJavadocParam.java new file mode 100644 index 00000000000..aee5e648c42 --- /dev/null +++ b/java/ql/src/Advisory/Documentation/SpuriousJavadocParam.java @@ -0,0 +1,52 @@ +/** + * BAD: The following param tag is empty. + * + * @param + */ +public void emptyParamTag(int p){ ... } + + +/** + * BAD: The following param tag has a misspelled value. + * + * @param prameter The parameter's value. + */ +public void typo(int parameter){ ... } + + +/** + * BAD: The following param tag appears to be outdated + * since the method does not take any parameters. + * + * @param sign The number's sign. + */ +public void outdated(){ ... } + + +/** + * BAD: The following param tag uses html within the tag value. + * + * @param ordinate The value of the y coordinate. + */ +public void html(int ordinate){ ... } + + +/** + * BAD: Invalid syntax for type parameter. + * + * @param T The type of the parameter. + * @param parameter The parameter value. + */ +public void parameterized(T parameter){ ... } + + +/** + * GOOD: A proper Javadoc comment. + * + * This method calculates the absolute value of a given number. + * + * @param The number's type. + * @param x The number to calculate the absolute value of. + * @return The absolute value of x. + */ +public T abs(T x){ ... } diff --git a/java/ql/src/Advisory/Documentation/SpuriousJavadocParam.qhelp b/java/ql/src/Advisory/Documentation/SpuriousJavadocParam.qhelp new file mode 100644 index 00000000000..001f4416bce --- /dev/null +++ b/java/ql/src/Advisory/Documentation/SpuriousJavadocParam.qhelp @@ -0,0 +1,42 @@ + + + + + +

    +Javadoc comments for public methods and constructors should use the @param tag to describe the available +parameters. If the comment includes any empty, incorrect or outdated parameter names then this will make +the documentation more difficult to read. +

    + +
    + + +

    The Javadoc comment for a method or constructor should always use non-empty @param values that match actual parameter or type parameter names.

    + +
    + + +

    The following example shows good and bad Javadoc comments that use the @param tag.

    + + + +
    + + + +
  • + Help - Eclipse Platform: + Java Compiler Javadoc Preferences. +
  • +
  • + Java SE Documentation: + How to Write Doc Comments for the Javadoc Tool, + The Java API Documentation Generator +
  • + + +
    +
    diff --git a/java/ql/src/Advisory/Documentation/SpuriousJavadocParam.ql b/java/ql/src/Advisory/Documentation/SpuriousJavadocParam.ql new file mode 100644 index 00000000000..0828e4dd85e --- /dev/null +++ b/java/ql/src/Advisory/Documentation/SpuriousJavadocParam.ql @@ -0,0 +1,33 @@ +/** + * @name Spurious Javadoc @param tags + * @description Javadoc @param tags that do not match any parameters in the method or constructor are confusing. + * @kind problem + * @problem.severity recommendation + * @precision very-high + * @id java/unknown-javadoc-parameter + * @tags maintainability + */ + +import java + +from Callable callable, ParamTag paramTag, string what, string msg +where + callable.(Documentable).getJavadoc().getAChild() = paramTag and + ( if callable instanceof Constructor + then what = "constructor" + else what = "method" + ) and + if exists(paramTag.getParamName()) then ( + // The tag's value is neither matched by a callable parameter name ... + not callable.getAParameter().getName() = paramTag.getParamName() and + // ... nor by a type parameter name. + not exists(TypeVariable tv | tv.getGenericCallable() = callable | + "<" + tv.getName() + ">" = paramTag.getParamName() + ) and + msg = "@param tag \"" + paramTag.getParamName() + + "\" does not match any actual parameter of " + what + " \"" + + callable.getName() + "()\"." + ) else + // The tag has no value at all. + msg = "This @param tag does not have a value." +select paramTag, msg diff --git a/java/ql/src/Advisory/Java Objects/AvoidCloneMethodAccess.qhelp b/java/ql/src/Advisory/Java Objects/AvoidCloneMethodAccess.qhelp new file mode 100644 index 00000000000..2f442ad4641 --- /dev/null +++ b/java/ql/src/Advisory/Java Objects/AvoidCloneMethodAccess.qhelp @@ -0,0 +1,7 @@ + + + + + diff --git a/java/ql/src/Advisory/Java Objects/AvoidCloneMethodAccess.ql b/java/ql/src/Advisory/Java Objects/AvoidCloneMethodAccess.ql new file mode 100644 index 00000000000..732ebbe8de0 --- /dev/null +++ b/java/ql/src/Advisory/Java Objects/AvoidCloneMethodAccess.ql @@ -0,0 +1,23 @@ +/** + * @name Use of clone() method + * @description Calling a method that overrides 'Object.clone' is bad practice. Copying an object + * using the 'Cloneable interface' and 'Object.clone' is error-prone. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/use-of-clone-method + * @tags reliability + */ +import java + +class ObjectCloneMethod extends Method { + ObjectCloneMethod() { + this.getDeclaringType() instanceof TypeObject and + this.getName() = "clone" and + this.hasNoParameters() + } +} + +from MethodAccess ma, ObjectCloneMethod clone +where ma.getMethod().overrides(clone) +select ma, "Invoking a method that overrides clone() should be avoided." diff --git a/java/ql/src/Advisory/Java Objects/AvoidCloneOverride.qhelp b/java/ql/src/Advisory/Java Objects/AvoidCloneOverride.qhelp new file mode 100644 index 00000000000..2f442ad4641 --- /dev/null +++ b/java/ql/src/Advisory/Java Objects/AvoidCloneOverride.qhelp @@ -0,0 +1,7 @@ + + + + + diff --git a/java/ql/src/Advisory/Java Objects/AvoidCloneOverride.ql b/java/ql/src/Advisory/Java Objects/AvoidCloneOverride.ql new file mode 100644 index 00000000000..4914dd8fdc6 --- /dev/null +++ b/java/ql/src/Advisory/Java Objects/AvoidCloneOverride.ql @@ -0,0 +1,25 @@ +/** + * @name Overriding definition of clone() + * @description Overriding 'Object.clone' is bad practice. Copying an object using the 'Cloneable + * interface' and 'Object.clone' is error-prone. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/override-of-clone-method + * @tags reliability + */ +import java + +class ObjectCloneMethod extends Method { + ObjectCloneMethod() { + this.getDeclaringType() instanceof TypeObject and + this.getName() = "clone" and + this.hasNoParameters() + } +} + +from Method m, ObjectCloneMethod clone +where + m.fromSource() and + m.overrides(clone) +select m, "Overriding the Object.clone() method should be avoided." diff --git a/java/ql/src/Advisory/Java Objects/AvoidCloneableInterface.java b/java/ql/src/Advisory/Java Objects/AvoidCloneableInterface.java new file mode 100644 index 00000000000..92473581b3f --- /dev/null +++ b/java/ql/src/Advisory/Java Objects/AvoidCloneableInterface.java @@ -0,0 +1,15 @@ +public final class Galaxy { + + // This is the original constructor. + public Galaxy (double aMass, String aName) { + fMass = aMass; + fName = aName; + } + + // This is the copy constructor. + public Galaxy(Galaxy aGalaxy) { + this(aGalaxy.getMass(), aGalaxy.getName()); + } + + // ... +} \ No newline at end of file diff --git a/java/ql/src/Advisory/Java Objects/AvoidCloneableInterface.qhelp b/java/ql/src/Advisory/Java Objects/AvoidCloneableInterface.qhelp new file mode 100644 index 00000000000..1f32c259046 --- /dev/null +++ b/java/ql/src/Advisory/Java Objects/AvoidCloneableInterface.qhelp @@ -0,0 +1,68 @@ + + + + + +

    +Copying an object using the Cloneable interface and the Object.clone method +is error-prone. This is because the Cloneable interface and the clone +method are unusual:

    + +
      +
    • The Cloneable interface has no methods. Its only use is to trigger different +behavior of Object.clone.
    • +
    • Object.clone is protected.
    • +
    • Object.clone creates a shallow copy without calling a constructor.
    • +
    + +

    The first two points mean that a programmer must do two things to get a useful implementation of +clone: first, make the class implement Cloneable to change the behavior of +Object.clone so that it makes a copy instead of throwing a CloneNotSupportedException; +second, override clone to make it public, to allow it to be called. Another +consequence of Cloneable not having any methods is that it does not say anything about +an object that implements it, which means that you cannot perform a polymorphic clone operation.

    + +

    The third point, Object.clone creating a shallow copy, is the most serious one. A +shallow copy shares internal state with the original object. This includes private +fields that the programmer might not be aware of. A change to the internal state of the original +object could affect the copy, and conversely the opposite is true, which could easily lead to +unexpected behavior.

    + +
    + + +

    +Define either a dedicated copy method or a copy constructor (with a parameter whose type is the same +as the type that declares the constructor). In most cases, this is at least as good as +using the Cloneable interface and the Object.clone method, without the +subtlety involved in implementing and using clone correctly. +

    + +
    + + +

    In the following example, class Galaxy includes a copy constructor. Its parameter is +of type Galaxy.

    + + + +
    + + + +
  • + J. Bloch, Effective Java (second edition), + Item 11. + Addison-Wesley, 2008. +
  • +
  • + Java Platform, Standard Edition 6, API Specification: + Interface Cloneable, + Object.clone. +
  • + + +
    +
    diff --git a/java/ql/src/Advisory/Java Objects/AvoidCloneableInterface.ql b/java/ql/src/Advisory/Java Objects/AvoidCloneableInterface.ql new file mode 100644 index 00000000000..3c4c1f227d2 --- /dev/null +++ b/java/ql/src/Advisory/Java Objects/AvoidCloneableInterface.ql @@ -0,0 +1,17 @@ +/** + * @name Use of Cloneable interface + * @description Using the 'Cloneable' interface is bad practice. Copying an object using the + * 'Cloneable interface' and 'Object.clone' is error-prone. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/use-of-cloneable-interface + * @tags reliability + */ +import java + +from RefType t +where + t.fromSource() and + t.getASupertype() instanceof TypeCloneable +select t, "This type implements or extends Cloneable, which should be avoided." diff --git a/java/ql/src/Advisory/Java Objects/AvoidFinalizeOverride.qhelp b/java/ql/src/Advisory/Java Objects/AvoidFinalizeOverride.qhelp new file mode 100644 index 00000000000..f32a74e2910 --- /dev/null +++ b/java/ql/src/Advisory/Java Objects/AvoidFinalizeOverride.qhelp @@ -0,0 +1,39 @@ + + + + + +

    +Overriding the Object.finalize method is +not a reliable way to terminate use of resources. +In particular, there are no guarantees regarding the timeliness of +finalizer execution. +

    + +
    + + +

    +Provide explicit termination methods, which should be +called by users of an API. +

    + +
    + + + +
  • + J. Bloch, Effective Java (second edition), + Item 7. + Addison-Wesley, 2008. +
  • +
  • + Java Language Specification: + 12.6. Finalization of Class Instances. +
  • + + +
    +
    diff --git a/java/ql/src/Advisory/Java Objects/AvoidFinalizeOverride.ql b/java/ql/src/Advisory/Java Objects/AvoidFinalizeOverride.ql new file mode 100644 index 00000000000..de5aa3a9873 --- /dev/null +++ b/java/ql/src/Advisory/Java Objects/AvoidFinalizeOverride.ql @@ -0,0 +1,24 @@ +/** + * @name Overriding definition of finalize() + * @description Overriding 'Object.finalize' is not a reliable way to terminate use of resources. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/override-of-finalize-method + * @tags reliability + */ +import java + +class ObjectFinalizeMethod extends Method { + ObjectFinalizeMethod() { + this.getDeclaringType() instanceof TypeObject and + this.getName() = "finalize" and + this.hasNoParameters() + } +} + +from Method m, ObjectFinalizeMethod finalize +where + m.fromSource() and + m.overrides(finalize) +select m, "Overriding the Object.finalize() method should be avoided." diff --git a/java/ql/src/Advisory/Naming/NamingConventionsCommon.qll b/java/ql/src/Advisory/Naming/NamingConventionsCommon.qll new file mode 100644 index 00000000000..e27c5f08157 --- /dev/null +++ b/java/ql/src/Advisory/Naming/NamingConventionsCommon.qll @@ -0,0 +1,8 @@ +import java + +class ConstantField extends Field { + ConstantField() { + this.isStatic() and + this.isFinal() + } +} diff --git a/java/ql/src/Advisory/Naming/NamingConventionsConstants.qhelp b/java/ql/src/Advisory/Naming/NamingConventionsConstants.qhelp new file mode 100644 index 00000000000..5b7b68af8e7 --- /dev/null +++ b/java/ql/src/Advisory/Naming/NamingConventionsConstants.qhelp @@ -0,0 +1,40 @@ + + + + + +

    A static, final field name that contains lowercase letters does not follow standard +naming conventions, which decreases code readability. For example, Min_Width. +

    + +
    + + +

    +Use uppercase letters throughout a static, final field name, and use underscores to +separate words within the field name. For example, MIN_WIDTH. +

    + +
    + + + +
  • + J. Bloch, Effective Java (second edition), + Item 56. + Addison-Wesley, 2008. +
  • +
  • + Java Language Specification: + 6.1. Declarations. +
  • +
  • + Java SE Documentation: + 9 - Naming Conventions. +
  • + + +
    +
    diff --git a/java/ql/src/Advisory/Naming/NamingConventionsConstants.ql b/java/ql/src/Advisory/Naming/NamingConventionsConstants.ql new file mode 100644 index 00000000000..2d2b54d2d9e --- /dev/null +++ b/java/ql/src/Advisory/Naming/NamingConventionsConstants.ql @@ -0,0 +1,19 @@ +/** + * @name Misnamed static final field + * @description A static, final field name that contains lowercase letters decreases readability. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/misnamed-constant + * @tags maintainability + */ +import java +import NamingConventionsCommon + +from ConstantField f +where + f.fromSource() and + not f.getName() = "serialVersionUID" and + f.getType() instanceof ImmutableType and + not f.getName().toUpperCase() = f.getName() +select f, "Static final fields should not contain lowercase letters." diff --git a/java/ql/src/Advisory/Naming/NamingConventionsMethods.qhelp b/java/ql/src/Advisory/Naming/NamingConventionsMethods.qhelp new file mode 100644 index 00000000000..404f49930bd --- /dev/null +++ b/java/ql/src/Advisory/Naming/NamingConventionsMethods.qhelp @@ -0,0 +1,40 @@ + + + + + +

    A method name that begins with an uppercase letter does not follow standard +naming conventions, which decreases code readability. For example, Getbackground. +

    + +
    + + +

    +Begin the method name with a lowercase letter and use camel case: capitalize the first letter of each word within +the method name. For example, getBackground. +

    + +
    + + + +
  • + J. Bloch, Effective Java (second edition), + Item 56. + Addison-Wesley, 2008. +
  • +
  • + Java Language Specification: + 6.1. Declarations. +
  • +
  • + Java SE Documentation: + 9 - Naming Conventions. +
  • + + +
    +
    diff --git a/java/ql/src/Advisory/Naming/NamingConventionsMethods.ql b/java/ql/src/Advisory/Naming/NamingConventionsMethods.ql new file mode 100644 index 00000000000..2be29008b65 --- /dev/null +++ b/java/ql/src/Advisory/Naming/NamingConventionsMethods.ql @@ -0,0 +1,16 @@ +/** + * @name Misnamed method + * @description A method name that begins with an uppercase letter decreases readability. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/misnamed-function + * @tags maintainability + */ +import java + +from Method m +where + m.fromSource() and + not m.getName().substring(0, 1).toLowerCase() = m.getName().substring(0, 1) +select m, "Method names should start in lowercase." diff --git a/java/ql/src/Advisory/Naming/NamingConventionsPackages.qhelp b/java/ql/src/Advisory/Naming/NamingConventionsPackages.qhelp new file mode 100644 index 00000000000..02586ca0ded --- /dev/null +++ b/java/ql/src/Advisory/Naming/NamingConventionsPackages.qhelp @@ -0,0 +1,39 @@ + + + + + +

    A package name that contains uppercase letters does not follow standard +naming conventions, which decreases code readability. For example, Com.Sun.Eng. +

    + +
    + + +

    +Use lowercase letters throughout a package name. For example, com.sun.eng. +

    + +
    + + + +
  • + J. Bloch, Effective Java (second edition), + Item 56. + Addison-Wesley, 2008. +
  • +
  • + Java Language Specification: + 6.1. Declarations. +
  • +
  • + Java SE Documentation: + 9 - Naming Conventions. +
  • + + +
    +
    diff --git a/java/ql/src/Advisory/Naming/NamingConventionsPackages.ql b/java/ql/src/Advisory/Naming/NamingConventionsPackages.ql new file mode 100644 index 00000000000..7e99f055739 --- /dev/null +++ b/java/ql/src/Advisory/Naming/NamingConventionsPackages.ql @@ -0,0 +1,17 @@ +/** + * @name Misnamed package + * @description A package name that contains uppercase letters decreases readability. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/misnamed-package + * @tags maintainability + */ +import java + +from RefType t, Package p +where + p = t.getPackage() and + t.fromSource() and + not p.getName().toLowerCase() = p.getName() +select t, "This type belongs to the package " + p.getName() + ", which should not include uppercase letters." diff --git a/java/ql/src/Advisory/Naming/NamingConventionsRefTypes.qhelp b/java/ql/src/Advisory/Naming/NamingConventionsRefTypes.qhelp new file mode 100644 index 00000000000..ccfa2171932 --- /dev/null +++ b/java/ql/src/Advisory/Naming/NamingConventionsRefTypes.qhelp @@ -0,0 +1,40 @@ + + + + + +

    A class or interface name that begins with a lowercase letter does not follow standard +naming conventions, which decreases code readability. For example, hotelbooking. +

    + +
    + + +

    +Begin the class name with an uppercase letter and use camel case: capitalize the first letter of each word within +the class name. For example, HotelBooking. +

    + +
    + + + +
  • + J. Bloch, Effective Java (second edition), + Item 56. + Addison-Wesley, 2008. +
  • +
  • + Java Language Specification: + 6.1. Declarations. +
  • +
  • + Java SE Documentation: + 9 - Naming Conventions. +
  • + + +
    +
    diff --git a/java/ql/src/Advisory/Naming/NamingConventionsRefTypes.ql b/java/ql/src/Advisory/Naming/NamingConventionsRefTypes.ql new file mode 100644 index 00000000000..e52098b8da8 --- /dev/null +++ b/java/ql/src/Advisory/Naming/NamingConventionsRefTypes.ql @@ -0,0 +1,17 @@ +/** + * @name Misnamed class or interface + * @description A class or interface name that begins with a lowercase letter decreases readability. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/misnamed-type + * @tags maintainability + */ +import java + +from RefType t +where + t.fromSource() and + not t instanceof AnonymousClass and + not t.getName().substring(0, 1).toUpperCase() = t.getName().substring(0, 1) +select t, "Class and interface names should start in uppercase." diff --git a/java/ql/src/Advisory/Naming/NamingConventionsVariables.qhelp b/java/ql/src/Advisory/Naming/NamingConventionsVariables.qhelp new file mode 100644 index 00000000000..c5b255e4f77 --- /dev/null +++ b/java/ql/src/Advisory/Naming/NamingConventionsVariables.qhelp @@ -0,0 +1,41 @@ + + + + + +

    A variable name that begins with an uppercase letter does not follow standard +naming conventions, which decreases code readability. For example, Numberofguests. +This applies to local variables, parameters, and non-constant fields. +

    + +
    + + +

    +Begin the variable name with a lowercase letter and use camel case: capitalize the first letter of each word within +the variable name. For example, numberOfGuests. +

    + +
    + + + +
  • + J. Bloch, Effective Java (second edition), + Item 56. + Addison-Wesley, 2008. +
  • +
  • + Java Language Specification: + 6.1. Declarations. +
  • +
  • + Java SE Documentation: + 9 - Naming Conventions. +
  • + + +
    +
    diff --git a/java/ql/src/Advisory/Naming/NamingConventionsVariables.ql b/java/ql/src/Advisory/Naming/NamingConventionsVariables.ql new file mode 100644 index 00000000000..2339c165b53 --- /dev/null +++ b/java/ql/src/Advisory/Naming/NamingConventionsVariables.ql @@ -0,0 +1,18 @@ +/** + * @name Misnamed variable + * @description A variable name that begins with an uppercase letter decreases readability. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/misnamed-variable + * @tags maintainability + */ +import java +import NamingConventionsCommon + +from Variable v +where + v.fromSource() and + not v instanceof ConstantField and + v.getName().substring(0, 1).toLowerCase() != v.getName().substring(0, 1) +select v, "Variable names should start in lowercase." diff --git a/java/ql/src/Advisory/Statements/MissingDefaultInSwitch.java b/java/ql/src/Advisory/Statements/MissingDefaultInSwitch.java new file mode 100644 index 00000000000..c88e90de942 --- /dev/null +++ b/java/ql/src/Advisory/Statements/MissingDefaultInSwitch.java @@ -0,0 +1,16 @@ +int menuChoice; + +// ... + +switch (menuChoice) { + case 1: + System.out.println("You chose number 1."); + break; + case 2: + System.out.println("You chose number 2."); + break; + case 3: + System.out.println("You chose number 3."); + break; + // BAD: No 'default' case +} \ No newline at end of file diff --git a/java/ql/src/Advisory/Statements/MissingDefaultInSwitch.qhelp b/java/ql/src/Advisory/Statements/MissingDefaultInSwitch.qhelp new file mode 100644 index 00000000000..847c24db682 --- /dev/null +++ b/java/ql/src/Advisory/Statements/MissingDefaultInSwitch.qhelp @@ -0,0 +1,47 @@ + + + + + +

    +A switch statement without a default case may allow execution to 'fall +through' silently, if no cases are matched. +

    + +
    + + +

    +In a switch statement that is based on a variable of a non-enumerated type, include a +default case to prevent execution from falling through silently when no cases are matched. +If the default case is intended to be unreachable code, it is advisable that it throws a +RuntimeException to alert the user of an internal error. +

    + +
    + + +

    In the following example, the switch statement outputs the menu choice that the user +has made. However, if the user does not choose 1, 2, or 3, execution falls through silently.

    + + + +

    In the following modified example, the switch statement includes a +default case, to allow for the user making an invalid menu choice.

    + + + +
    + + + +
  • + Java SE Documentation: + 7.8 switch Statements. +
  • + + +
    +
    diff --git a/java/ql/src/Advisory/Statements/MissingDefaultInSwitch.ql b/java/ql/src/Advisory/Statements/MissingDefaultInSwitch.ql new file mode 100644 index 00000000000..eab8a596f0e --- /dev/null +++ b/java/ql/src/Advisory/Statements/MissingDefaultInSwitch.ql @@ -0,0 +1,19 @@ +/** + * @name Missing default case in switch + * @description A 'switch' statement that is based on a non-enumerated type and that does not have a + * 'default' case may allow execution to 'fall through' silently. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/missing-default-in-switch + * @tags reliability + * external/cwe/cwe-478 + */ + +import java + +from SwitchStmt switch +where + not switch.getExpr().getType() instanceof EnumType and + not exists(switch.getDefaultCase()) +select switch, "Switch statement does not have a default case." diff --git a/java/ql/src/Advisory/Statements/MissingDefaultInSwitchGood.java b/java/ql/src/Advisory/Statements/MissingDefaultInSwitchGood.java new file mode 100644 index 00000000000..435b95c45b9 --- /dev/null +++ b/java/ql/src/Advisory/Statements/MissingDefaultInSwitchGood.java @@ -0,0 +1,18 @@ +int menuChoice; + +// ... + +switch (menuChoice) { + case 1: + System.out.println("You chose number 1."); + break; + case 2: + System.out.println("You chose number 2."); + break; + case 3: + System.out.println("You chose number 3."); + break; + default: // GOOD: 'default' case for invalid choices + System.out.println("Sorry, you made an invalid choice."); + break; +} \ No newline at end of file diff --git a/java/ql/src/Advisory/Statements/OneStatementPerLine.qhelp b/java/ql/src/Advisory/Statements/OneStatementPerLine.qhelp new file mode 100644 index 00000000000..49f7dec654b --- /dev/null +++ b/java/ql/src/Advisory/Statements/OneStatementPerLine.qhelp @@ -0,0 +1,28 @@ + + + + + +

    Code where each statement is defined on a separate line is much easier for programmers to read than code where multiple statements are defined on the same line. +

    + +
    + + +

    Separate statements by a newline character. +

    + +
    + + + +
  • + Java SE Documentation: + 7.1 Simple Statements. +
  • + + +
    +
    diff --git a/java/ql/src/Advisory/Statements/OneStatementPerLine.ql b/java/ql/src/Advisory/Statements/OneStatementPerLine.ql new file mode 100644 index 00000000000..50e1a927ca3 --- /dev/null +++ b/java/ql/src/Advisory/Statements/OneStatementPerLine.ql @@ -0,0 +1,44 @@ +/** + * @name Multiple statements on line + * @description More than one statement per line decreases readability. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/multiple-statements-on-same-line + * @tags maintainability + */ +import java + +predicate lineDefinesEnum(File f, int line) { + exists(Location l | + exists(EnumType e | e.getLocation() = l) or + exists(EnumConstant e | e.getLocation() = l) | + f = l.getFile() and line = l.getStartLine() + ) +} + +predicate oneLineStatement(Stmt s, File f, int line, int col) { + exists(Location l | s.getLocation() = l | + f = l.getFile() and + line = l.getStartLine() and + line = l.getEndLine() and + col = l.getStartColumn() + ) and + // Exclude blocks: `{break;}` is not really a violation. + not s instanceof Block and + // Exclude implicit super constructor invocations. + not s instanceof SuperConstructorInvocationStmt and + // Java enums are desugared to a whole bunch of generated statements. + not lineDefinesEnum(f, line) +} + +from Stmt s, Stmt s2 +where + exists(File f, int line, int col, int col2 | + oneLineStatement(s, f, line, col) and + oneLineStatement(s2, f, line, col2) and + col < col2 and + // Don't report multiple results if more than 2 statements are on a single line. + col = min(int otherCol | oneLineStatement(_, f, line, otherCol)) + ) +select s, "This statement is followed by another on the same line." diff --git a/java/ql/src/Advisory/Statements/TerminateIfElseIfWithElse.java b/java/ql/src/Advisory/Statements/TerminateIfElseIfWithElse.java new file mode 100644 index 00000000000..d30108a9e0c --- /dev/null +++ b/java/ql/src/Advisory/Statements/TerminateIfElseIfWithElse.java @@ -0,0 +1,16 @@ +int score; +char grade; + +// ... + +if (score >= 90) { + grade = 'A'; +} else if (score >= 80) { + grade = 'B'; +} else if (score >= 70) { + grade = 'C'; +} else if (score >= 60) { + grade = 'D'; + // BAD: No terminating 'else' clause +} +System.out.println("Grade = " + grade); \ No newline at end of file diff --git a/java/ql/src/Advisory/Statements/TerminateIfElseIfWithElse.qhelp b/java/ql/src/Advisory/Statements/TerminateIfElseIfWithElse.qhelp new file mode 100644 index 00000000000..a50e1736cba --- /dev/null +++ b/java/ql/src/Advisory/Statements/TerminateIfElseIfWithElse.qhelp @@ -0,0 +1,48 @@ + + + + + +

    +An if-else-if statement without a terminating else +clause may allow execution to 'fall through' silently, if none of the if clauses are +matched. +

    + +
    + + +

    +Include a terminating else clause to if-else-if statements +to prevent execution from falling through silently. If the terminating else clause is +intended to be unreachable code, it is advisable that it throws a RuntimeException to +alert the user of an internal error. +

    + +
    + + +

    In the following example, the if statement outputs the grade that is achieved depending on the +test score. However, if the score is less than 60, execution falls through silently.

    + + + +

    In the following modified example, the if statement includes a terminating +else clause, to allow for scores that are less than 60.

    + + + +
    + + + +
  • + Java SE Documentation: + 7.4 if, if-else, if else-if else Statements. +
  • + + +
    +
    diff --git a/java/ql/src/Advisory/Statements/TerminateIfElseIfWithElse.ql b/java/ql/src/Advisory/Statements/TerminateIfElseIfWithElse.ql new file mode 100644 index 00000000000..0172719094c --- /dev/null +++ b/java/ql/src/Advisory/Statements/TerminateIfElseIfWithElse.ql @@ -0,0 +1,18 @@ +/** + * @name Non-terminated if-else-if chain + * @description An 'if-else-if' statement without a terminating 'else' clause may allow execution to + * 'fall through' silently. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/non-terminated-if-else-if-chain + * @tags reliability + */ + +import java + +from IfStmt prev, IfStmt last +where + not exists(last.getElse()) and + prev.getElse() = last +select last, "If-else-if statement does not have a terminating else statement." diff --git a/java/ql/src/Advisory/Statements/TerminateIfElseIfWithElseGood.java b/java/ql/src/Advisory/Statements/TerminateIfElseIfWithElseGood.java new file mode 100644 index 00000000000..1476a14d7ff --- /dev/null +++ b/java/ql/src/Advisory/Statements/TerminateIfElseIfWithElseGood.java @@ -0,0 +1,17 @@ +int score; +char grade; + +// ... + +if (score >= 90) { + grade = 'A'; +} else if (score >= 80) { + grade = 'B'; +} else if (score >= 70) { + grade = 'C'; +} else if (score >= 60) { + grade = 'D'; +} else { // GOOD: Terminating 'else' clause for all other scores + grade = 'F'; +} +System.out.println("Grade = " + grade); \ No newline at end of file diff --git a/java/ql/src/Advisory/Types/GenericsConstructor.qhelp b/java/ql/src/Advisory/Types/GenericsConstructor.qhelp new file mode 100644 index 00000000000..301395a9df9 --- /dev/null +++ b/java/ql/src/Advisory/Types/GenericsConstructor.qhelp @@ -0,0 +1,9 @@ + + + + + + + diff --git a/java/ql/src/Advisory/Types/GenericsConstructor.ql b/java/ql/src/Advisory/Types/GenericsConstructor.ql new file mode 100644 index 00000000000..613d78b6346 --- /dev/null +++ b/java/ql/src/Advisory/Types/GenericsConstructor.ql @@ -0,0 +1,15 @@ +/** + * @name Non-parameterized constructor invocation + * @description Parameterizing a call to a constructor of a generic type increases type safety and + * code readability. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/raw-constructor-invocation + * @tags maintainability + */ +import java + +from ClassInstanceExpr cie +where cie.getConstructor().getDeclaringType() instanceof RawType +select cie, "This is a non-parameterized constructor invocation of a generic type." diff --git a/java/ql/src/Advisory/Types/GenericsReturnType.qhelp b/java/ql/src/Advisory/Types/GenericsReturnType.qhelp new file mode 100644 index 00000000000..301395a9df9 --- /dev/null +++ b/java/ql/src/Advisory/Types/GenericsReturnType.qhelp @@ -0,0 +1,9 @@ + + + + + + + diff --git a/java/ql/src/Advisory/Types/GenericsReturnType.ql b/java/ql/src/Advisory/Types/GenericsReturnType.ql new file mode 100644 index 00000000000..1160dfc21a7 --- /dev/null +++ b/java/ql/src/Advisory/Types/GenericsReturnType.ql @@ -0,0 +1,17 @@ +/** + * @name Non-parameterized method return type + * @description Using a parameterized instance of a generic type for a method return type increases + * type safety and code readability. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/raw-return-type + * @tags maintainability + */ +import java + +from Method m +where + m.fromSource() and + m.getReturnType() instanceof RawType +select m, "This method has a non-parameterized return type." diff --git a/java/ql/src/Advisory/Types/GenericsVariable.qhelp b/java/ql/src/Advisory/Types/GenericsVariable.qhelp new file mode 100644 index 00000000000..301395a9df9 --- /dev/null +++ b/java/ql/src/Advisory/Types/GenericsVariable.qhelp @@ -0,0 +1,9 @@ + + + + + + + diff --git a/java/ql/src/Advisory/Types/GenericsVariable.ql b/java/ql/src/Advisory/Types/GenericsVariable.ql new file mode 100644 index 00000000000..ad0fa8cbc37 --- /dev/null +++ b/java/ql/src/Advisory/Types/GenericsVariable.ql @@ -0,0 +1,17 @@ +/** + * @name Non-parameterized variable + * @description Declaring a field, parameter, or local variable as a parameterized type increases + * type safety and code readability. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/raw-variable + * @tags maintainability + */ +import java + +from Variable v +where + v.fromSource() and + v.getType() instanceof RawType +select v, "This declaration uses a non-parameterized type." diff --git a/java/ql/src/Advisory/Types/Generics_Common.java b/java/ql/src/Advisory/Types/Generics_Common.java new file mode 100644 index 00000000000..f2f75c772ad --- /dev/null +++ b/java/ql/src/Advisory/Types/Generics_Common.java @@ -0,0 +1,6 @@ +public List constructRawList(Object o) { + List list; // Raw variable declaration + list = new ArrayList(); // Raw constructor call + list.add(o); + return list; // Raw method return type (see signature above) +} \ No newline at end of file diff --git a/java/ql/src/Advisory/Types/Generics_Common.qhelp b/java/ql/src/Advisory/Types/Generics_Common.qhelp new file mode 100644 index 00000000000..617e493e7a5 --- /dev/null +++ b/java/ql/src/Advisory/Types/Generics_Common.qhelp @@ -0,0 +1,56 @@ + + + +

    +The use of generics in Java improves compile-time type safety and +code readability. +Users of a class or interface that has been designed using generic types +should therefore make use of parameterized instances +in variable declarations, method return types, and constructor calls. +

    + +
    + + +

    +Provide type parameters to generic classes and interfaces where possible. +

    +

    +Note that converting legacy code to use generics may +have to be done carefully in order to preserve the existing +functionality of an API; for detailed guidance, see the references. +

    + +
    + +

    The following example is poorly written because it uses raw types. This makes it +more error prone because the compiler is less able to perform type checks.

    + + +

    A parameterized version can be easily made and is much safer.

    + + +
    + + + +
  • + J. Bloch, Effective Java (second edition), + Item 23. + Addison-Wesley, 2008. +
  • +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • +
  • + The Java Tutorials: + Generics, + Converting Legacy Code to Use Generics. +
  • + + +
    +
    diff --git a/java/ql/src/Advisory/Types/Generics_CommonGood.java b/java/ql/src/Advisory/Types/Generics_CommonGood.java new file mode 100644 index 00000000000..8f68f23c493 --- /dev/null +++ b/java/ql/src/Advisory/Types/Generics_CommonGood.java @@ -0,0 +1,6 @@ +public List constructParameterizedList(T o) { + List list; // Parameterized variable declaration + list = new ArrayList(); // Parameterized constructor call + list.add(o); + return list; // Parameterized method return type (see signature above) +} \ No newline at end of file diff --git a/java/ql/src/AlertSuppression.ql b/java/ql/src/AlertSuppression.ql new file mode 100644 index 00000000000..142bf78e695 --- /dev/null +++ b/java/ql/src/AlertSuppression.ql @@ -0,0 +1,89 @@ +/** + * @name Alert suppression + * @description Generates information about alert suppressions. + * @kind alert-suppression + * @id java/alert-suppression + */ + +import java + +/** + * An alert suppression comment. + */ +class SuppressionComment extends Javadoc { + string annotation; + + SuppressionComment() { + isEolComment(this) and + exists(string text | text = getChild(0).getText() | + // match `lgtm[...]` anywhere in the comment + annotation = text.regexpFind("(?i)\\blgtm\\s*\\[[^\\]]*\\]", _, _) + or + // match `lgtm` at the start of the comment and after semicolon + annotation = text.regexpFind("(?i)(?<=^|;)\\s*lgtm(?!\\B|\\s*\\[)", _, _).trim() + ) + } + + /** + * Gets the text of this suppression comment. + */ + string getText() { + result = getChild(0).getText() + } + + /** Gets the suppression annotation in this comment. */ + string getAnnotation() { + result = annotation + } + + /** + * Holds if this comment applies to the range from column `startcolumn` of line `startline` + * to column `endcolumn` of line `endline` in file `filepath`. + */ + predicate covers(string filepath, int startline, int startcolumn, int endline, int endcolumn) { + this.getLocation().hasLocationInfo(filepath, startline, _, endline, endcolumn) and + startcolumn = 1 + } + + /** Gets the scope of this suppression. */ + SuppressionScope getScope() { + this = result.getSuppressionComment() + } +} + +/** + * The scope of an alert suppression comment. + */ +class SuppressionScope extends @javadoc { + SuppressionScope() { + this instanceof SuppressionComment + } + + /** Gets a suppression comment with this scope. */ + SuppressionComment getSuppressionComment() { + result = this + } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [LGTM locations](https://lgtm.com/help/ql/locations). + */ + predicate hasLocationInfo(string filepath, int startline, int startcolumn, int endline, int endcolumn) { + this.(SuppressionComment).covers(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets a textual representation of this element. */ + string toString() { + result = "suppression range" + } +} + +from SuppressionComment c +select + c, // suppression comment + c.getText(), // text of suppression comment (excluding delimiters) + c.getAnnotation(), // text of suppression annotation + c.getScope() // scope of suppression diff --git a/java/ql/src/Architecture/Dependencies/MutualDependency.java b/java/ql/src/Architecture/Dependencies/MutualDependency.java new file mode 100644 index 00000000000..5233ac7ec47 --- /dev/null +++ b/java/ql/src/Architecture/Dependencies/MutualDependency.java @@ -0,0 +1,32 @@ +public class MutualDependency { + // Violation: BadModel and BadView are mutually dependent + private static class BadModel { + private int i; + private BadView view; + + public int getI() { + return i; + } + + public void setI(int i) { + this.i = i; + if(view != null) view.modelChanged(); + } + + public void setView(BadView view) { + this.view = view; + } + } + + private static class BadView { + private BadModel model; + + public BadView(BadModel model) { + this.model = model; + } + + public void modelChanged() { + System.out.println("Model Changed: " + model.getI()); + } + } +} \ No newline at end of file diff --git a/java/ql/src/Architecture/Dependencies/MutualDependency.qhelp b/java/ql/src/Architecture/Dependencies/MutualDependency.qhelp new file mode 100644 index 00000000000..a1a0851e7f1 --- /dev/null +++ b/java/ql/src/Architecture/Dependencies/MutualDependency.qhelp @@ -0,0 +1,82 @@ + + + +

    +A mutual dependency exists when two code entities (for example, types or packages) depend directly on each other. +Mutual dependencies are caused by unwanted dependencies in one or both directions. There +are many different kinds of dependency; here are a few examples of how an inter-type dependency +from T1 to T2 can occur: +

    + +
      +
    • T1 derives from a type involving T2, for example T2 itself or List<T2>.
    • +
    • T1 declares a field of a type involving T2.
    • +
    • T1 declares a method whose return type involves T2.
    • +
    • A method of T1 declares a local variable whose type involves T2.
    • +
    • A method of T1 catches an exception of a type involving T2.
    • +
    + +

    +Mutual dependencies prevent you from considering either entity in isolation, +affecting readability and testability. For example, if types T1 and T2 depend on each other, then it is +generally impossible to fully understand T1 without understanding T2, and vice-versa. Moreover, neither +type can be tested without the other being present. Whilst mocking can alleviate this latter problem +to some extent, breaking the mutual dependency is a better solution. For example, suppose we could +remove all of the dependencies from T2 to T1 - in that case, we would be able to test T2 in isolation, and +completely side-step the need to provide a T1, mocked or otherwise. +

    + +
    + + +

    +Breaking mutual dependencies involves finding ways of removing the unwanted individual dependencies that +cause them. The way to do this depends on the kind of dependency in question, with some kinds (for example, +dependencies caused by inheritance) being much harder to break than others. A full list of ways to +break cycles is beyond the scope of this help topic, however, a few high-level techniques +for breaking a dependency from T1 to T2 include: +

    + +
      +
    • +Introducing an interface that is implemented by T2. T1 +can then be refactored to use T2 only via the interface, which breaks the cycle. +
    • + +
    • +Moving the depended-on code in T2 to a third (possibly new) entity. +T1 can then depend on this third entity instead of on T2, +breaking the cycle. T2 is allowed to depend on the third entity as +well, although it does not have to if there is no need. +
    • + +
    • +Merging T1 and T2 together (for example, if there was an artificial separation between two parts +of the same concept). This is not a generally-applicable solution, but is sometimes the right thing to +do. It has the effect of internalizing the cycle, which is sufficient to solve the problem. +
    • +
    + +

    +For more information on how to break +unwanted dependencies, see the references (particularly [Lakos]). +

    + +
    + +

    In this example BadModel and BadView are mutually dependent.

    + +

    The interface technique can be used to break the dependency between the model and the view. The ModelListener interface allows BetterView to interact with BetterModel without dependency.

    + + +
    + + + +
  • J. Lakos. Large-Scale C++ Software Design. Addison-Wesley, 1996.
  • +
  • M. Fowler. Refactoring. Addison-Wesley, 1999.
  • + +
    +
    diff --git a/java/ql/src/Architecture/Dependencies/MutualDependency.ql b/java/ql/src/Architecture/Dependencies/MutualDependency.ql new file mode 100644 index 00000000000..e86cc7216b6 --- /dev/null +++ b/java/ql/src/Architecture/Dependencies/MutualDependency.ql @@ -0,0 +1,36 @@ +/** + * @name Mutually-dependent types + * @description Mutual dependency between types makes code difficult to understand and test. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/mutually-dependent-types + * @tags testability + * maintainability + * modularity + */ + +import java + +from RefType t1, RefType t2 +where + depends(t1, t2) and + depends(t2, t1) and + // Prevent symmetrical results. + t1.getName() < t2.getName() and + t1.fromSource() and + t2.fromSource() and + // Exclusions. + not ( + t1 instanceof AnonymousClass or + t1 instanceof BoundedType or + t2 instanceof AnonymousClass or + t2 instanceof BoundedType or + t1.getName().toLowerCase().matches("%visitor%") or + t2.getName().toLowerCase().matches("%visitor%") or + t1.getAMethod().getName().toLowerCase().matches("%visit%") or + t2.getAMethod().getName().toLowerCase().matches("%visit%") or + t1.getPackage() = t2.getPackage() + ) +select t1, "This type and type $@ are mutually dependent.", + t2, t2.getName() diff --git a/java/ql/src/Architecture/Dependencies/MutualDependencyFix.java b/java/ql/src/Architecture/Dependencies/MutualDependencyFix.java new file mode 100644 index 00000000000..86d37ac34f5 --- /dev/null +++ b/java/ql/src/Architecture/Dependencies/MutualDependencyFix.java @@ -0,0 +1,51 @@ +public class NoMutualDependency { + // Better: A new interface breaks the dependency + // from the model to the view + private interface ModelListener { + void modelChanged(); + } + + private static class BetterModel { + private int i; + private ModelListener listener; + + public int getI() { + return i; + } + + public void setI(int i) { + this.i = i; + if (listener != null) listener.modelChanged(); + } + + public void setListener(ModelListener listener) { + this.listener = listener; + } + } + + private static class BetterView implements ModelListener { + private BetterModel model; + + public BetterView(BetterModel model) { + this.model = model; + } + + public void modelChanged() { + System.out.println("Model Changed: " + model.getI()); + } + } + + public static void main(String[] args) { + BadModel badModel = new BadModel(); + BadView badView = new BadView(badModel); + badModel.setView(badView); + badModel.setI(23); + badModel.setI(9); + + BetterModel betterModel = new BetterModel(); + BetterView betterView = new BetterView(betterModel); + betterModel.setListener(betterView); + betterModel.setI(24); + betterModel.setI(12); + } +} \ No newline at end of file diff --git a/java/ql/src/Architecture/Dependencies/UnusedMavenDependencies.qll b/java/ql/src/Architecture/Dependencies/UnusedMavenDependencies.qll new file mode 100644 index 00000000000..58937c277e1 --- /dev/null +++ b/java/ql/src/Architecture/Dependencies/UnusedMavenDependencies.qll @@ -0,0 +1,26 @@ +import java +import semmle.code.xml.MavenPom + +/** + * Holds if the source code in the project represented by the given pom depends on any code + * within the given container (folder, jar file etc.). + */ +predicate pomDependsOnContainer(Pom f, Container g) { + exists(RefType source, RefType target | + source.getFile().getParentContainer*() = f.getFile().getParentContainer() and + target.getFile().getParentContainer*() = g and + depends(source, target) + ) +} + +/** + * Holds if the source code in the project represented by the sourcePom depends on any code + * within the project represented by the targetPom. + */ +predicate pomDependsOnPom(Pom sourcePom, Pom targetPom) { + exists(RefType source, RefType target | + source.getFile().getParentContainer*() = sourcePom.getFile().getParentContainer() and + target.getFile().getParentContainer*() = targetPom.getFile().getParentContainer() and + depends(source, target) + ) +} diff --git a/java/ql/src/Architecture/Dependencies/UnusedMavenDependency.qhelp b/java/ql/src/Architecture/Dependencies/UnusedMavenDependency.qhelp new file mode 100644 index 00000000000..b9178113bd8 --- /dev/null +++ b/java/ql/src/Architecture/Dependencies/UnusedMavenDependency.qhelp @@ -0,0 +1,35 @@ + + + +

    For projects that build with Maven, unnecessary dependencies add a variety +of maintenance burdens. Most immediately, unnecessary dependencies increase +build time, because Maven rebuilds an artifact whenever its declared +dependencies are modified. This rule identifies Maven dependencies that +are declared in a POM file but are not used by the underlying source code.

    + +

    If the dependency's source code is part of the code base being analyzed, +then the result is reported by one version of the rule. Otherwise, the +dependency is reported by a separate version of the rule. This allows +the two types of unused Maven dependencies to be reported separately.

    + +
    + +

    Try removing the dependency from the POM file. Then run all build and test +targets that are relevant for the modified POM file. If all of the relevant +build and test targets still succeed, then leave the dependency out permanently. +Doing so will make future maintenance of the relevant source code easier.

    + +

    In some cases, there may be a true dependency on the code that is not +detected by the analysis. If any of the build and test targets fail +after the dependency is removed, then the result is a false positive, +and the dependency should be restored.

    +
    + + +
  • Apache Maven Project: +Maven POM Reference: Dependencies. +
  • +
    +
    diff --git a/java/ql/src/Architecture/Dependencies/UnusedMavenDependencyBinary.qhelp b/java/ql/src/Architecture/Dependencies/UnusedMavenDependencyBinary.qhelp new file mode 100644 index 00000000000..17c21c500db --- /dev/null +++ b/java/ql/src/Architecture/Dependencies/UnusedMavenDependencyBinary.qhelp @@ -0,0 +1,6 @@ + + + + diff --git a/java/ql/src/Architecture/Dependencies/UnusedMavenDependencyBinary.ql b/java/ql/src/Architecture/Dependencies/UnusedMavenDependencyBinary.ql new file mode 100644 index 00000000000..021b17f3820 --- /dev/null +++ b/java/ql/src/Architecture/Dependencies/UnusedMavenDependencyBinary.ql @@ -0,0 +1,55 @@ +/** + * @name Unused Maven dependency (binary) + * @description Unnecessary Maven dependencies are a maintenance burden. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/unused-maven-binary-dependency + */ + +import UnusedMavenDependencies + +/* + * A whitelist of binary dependencies that should never be highlighted as unusued. + */ +predicate whitelist(Dependency d) { + /* + * jsr305 contains package annotations. If a project uses those exclusively, we will + * consider it "unused". + */ + d.getShortCoordinate() = "com.google.code.findbugs:jsr305" +} + +from PomDependency d, Pom source +where +source.getADependency() = d and +/* + * There is not a Pom file for the target of this dependency, so we assume that it was resolved by + * a binary file in the local maven repository. + */ +not exists(Pom target | target = d.getPom()) and +/* + * In order to accurately identify whether this binary dependency is required, we must have identified + * a Maven repository. If we have not found a repository, it's likely that it has a custom path of + * which we are unaware, so do not report any problems. + */ +exists(MavenRepo mr) and +/* + * We either haven't indexed a relevant jar file, which suggests that nothing statically depended upon + * it, or we have indexed the relevant jar file, but no source code in the project defined by the pom + * depends on any code within the detected jar. + */ +not pomDependsOnContainer(source, d.getJar()) and +/* + * If something that depends on us depends on the jar represented by this dependency, and it doesn't + * depend directly on the jar itself, we don't consider it to be "unused". + */ +not exists(Pom pomThatDependsOnSource | + pomThatDependsOnSource.getAnExportedPom+() = source + | + pomDependsOnContainer(pomThatDependsOnSource, d.getJar()) and + not exists(File f | f = pomThatDependsOnSource.getADependency().getJar() and f = d.getJar())) and +// Filter out those dependencies on the whitelist +not whitelist(d) +select d, "Maven dependency on the binary package " + d.getShortCoordinate() + " is unused." + diff --git a/java/ql/src/Architecture/Dependencies/UnusedMavenDependencySource.qhelp b/java/ql/src/Architecture/Dependencies/UnusedMavenDependencySource.qhelp new file mode 100644 index 00000000000..17c21c500db --- /dev/null +++ b/java/ql/src/Architecture/Dependencies/UnusedMavenDependencySource.qhelp @@ -0,0 +1,6 @@ + + + + diff --git a/java/ql/src/Architecture/Dependencies/UnusedMavenDependencySource.ql b/java/ql/src/Architecture/Dependencies/UnusedMavenDependencySource.ql new file mode 100644 index 00000000000..55bf10e934c --- /dev/null +++ b/java/ql/src/Architecture/Dependencies/UnusedMavenDependencySource.ql @@ -0,0 +1,34 @@ +/** + * @name Unused Maven dependency (source) + * @description Unnecessary Maven dependencies are a maintenance burden. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/unused-maven-source-dependency + */ + +import java +import semmle.code.xml.MavenPom +import UnusedMavenDependencies + +from PomDependency d, Pom source, Pom target +where +source.getADependency() = d and +/* + * We have a targetPom file, so this is a "source" dependency, rather than a binary dependency + * from the Maven repository. Note, although .pom files exist in the local maven repository, they + * are usually not indexed because they are outside the source directory. We assume that they have + * not been indexed. + */ +target = d.getPom() and +/* + * If we have a pom for the target of this dependency, then it is unused iff neither it, nor any + * of its transitive dependencies are required. + */ +not exists(Pom exported | + exported = target.getAnExportedPom*() + | + pomDependsOnContainer(source, exported.getAnExportedDependency().getJar()) or + pomDependsOnPom(source, exported) +) +select d, "Maven dependency onto " + d.getShortCoordinate() + " is unused." diff --git a/java/ql/src/Architecture/Refactoring Opportunities/DeeplyNestedClass.java b/java/ql/src/Architecture/Refactoring Opportunities/DeeplyNestedClass.java new file mode 100644 index 00000000000..b0021cc3849 --- /dev/null +++ b/java/ql/src/Architecture/Refactoring Opportunities/DeeplyNestedClass.java @@ -0,0 +1,41 @@ +class X1 { + private int i; + + public X1(int i) { + this.i = i; + } + + public Y1 makeY1(float j) { + return new Y1(j); + } + + class Y1 { + private float j; + + public Y1(float j) { + this.j = j; + } + + public Z1 makeZ1(double k) { + return new Z1(k); + } + + // Violation + class Z1 { + private double k; + + public Z1(double k) { + this.k = k; + } + + public void foo() { + System.out.println(i * j * k); + } + } + } +} +public class DeeplyNestedClass { + public static void main(String[] args) { + new X1(23).makeY1(9.0f).makeZ1(84.0).foo(); + } +} \ No newline at end of file diff --git a/java/ql/src/Architecture/Refactoring Opportunities/DeeplyNestedClass.qhelp b/java/ql/src/Architecture/Refactoring Opportunities/DeeplyNestedClass.qhelp new file mode 100644 index 00000000000..05f81435dd6 --- /dev/null +++ b/java/ql/src/Architecture/Refactoring Opportunities/DeeplyNestedClass.qhelp @@ -0,0 +1,46 @@ + + + +

    +Classes (especially complex ones) that are nested multiple levels deep can be difficult to understand because they have access to variables +from all of the classes that enclose them. Such classes can also be difficult to unit test. Specific exceptions are made for: +

    + +
      +
    • Anonymous classes - these are generally used as a substitute for closures.
    • +
    • Enumerations, and simple classes that contain no methods - these are unlikely to hinder readability.
    • +
    + +
    + + +

    +The solution is to move one or more of the nested classes into a higher scope, less deeply-nested (see example below). When you move a nested class, you must:

    +
      +
    • +Ensure that the class can still access the required variables from its previously enclosing scopes.
    • +
    • +Consider the dependencies, particularly when you move a non-static nested class out of the +containing class. Generally, a non-static class should be refactored to depend only on +the contents of the classes that previously enclosed it. This avoids introducing a dependency cycle where the non-static class depends on the previously-enclosing classes themselves. +
    + +
    + +

    In the following example Z1 is difficult to read because it is deeply nested.

    + + +

    In this example, there are no nested classes and you can clearly see which variables affect which class.

    + + +
    + + +
  • +The Java Tutorials: Nested Classes. +
  • + +
    +
    diff --git a/java/ql/src/Architecture/Refactoring Opportunities/DeeplyNestedClass.ql b/java/ql/src/Architecture/Refactoring Opportunities/DeeplyNestedClass.ql new file mode 100644 index 00000000000..aacc550c677 --- /dev/null +++ b/java/ql/src/Architecture/Refactoring Opportunities/DeeplyNestedClass.ql @@ -0,0 +1,19 @@ +/** + * @name Deeply-nested class + * @description Deeply-nested classes are difficult to understand, since they have access to many scopes + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/deeply-nested-class + * @tags testability + */ + +import java + +from NestedClass c +where + c.getNestingDepth() > 1 and + exists(Method m | m.getDeclaringType() = c | not m instanceof StaticInitializer) and + not c instanceof AnonymousClass and + not c instanceof EnumType +select c, "This class is deeply nested, which can hinder readability." diff --git a/java/ql/src/Architecture/Refactoring Opportunities/DeeplyNestedClassFix.java b/java/ql/src/Architecture/Refactoring Opportunities/DeeplyNestedClassFix.java new file mode 100644 index 00000000000..403a3f77dca --- /dev/null +++ b/java/ql/src/Architecture/Refactoring Opportunities/DeeplyNestedClassFix.java @@ -0,0 +1,47 @@ +class X2 { + private int i; + + public X2(int i) { + this.i = i; + } + + public Y2 makeY2(float j) { + return new Y2(i, j); + } +} + +class Y2 { + private int i; + private float j; + + public Y2(int i, float j) { + this.i = i; + this.j = j; + } + + public Z2 makeZ2(double k) { + return new Z2(i, j, k); + } +} + +class Z2 { + private int i; + private float j; + private double k; + + public Z2(int i, float j, double k) { + this.i = i; + this.j = j; + this.k = k; + } + + public void foo() { + System.out.println(i * j * k); + } +} + +public class NotNestedClass { + public static void main(String[] args) { + new X2(23).makeY2(9.0f).makeZ2(84.0).foo(); + } +} \ No newline at end of file diff --git a/java/ql/src/Architecture/Refactoring Opportunities/FeatureEnvy.java b/java/ql/src/Architecture/Refactoring Opportunities/FeatureEnvy.java new file mode 100644 index 00000000000..c25072494b6 --- /dev/null +++ b/java/ql/src/Architecture/Refactoring Opportunities/FeatureEnvy.java @@ -0,0 +1,22 @@ +// Before refactoring: +class Item { .. } +class Basket { + // .. + float getTotalPrice(Item i) { + float price = i.getPrice() + i.getTax(); + if (i.isOnSale()) + price = price - i.getSaleDiscount() * price; + return price; + } +} + +// After refactoring: +class Item { + // .. + float getTotalPrice() { + float price = getPrice() + getTax(); + if (isOnSale()) + price = price - getSaleDiscount() * price; + return price; + } +} \ No newline at end of file diff --git a/java/ql/src/Architecture/Refactoring Opportunities/FeatureEnvy.qhelp b/java/ql/src/Architecture/Refactoring Opportunities/FeatureEnvy.qhelp new file mode 100644 index 00000000000..f52777589df --- /dev/null +++ b/java/ql/src/Architecture/Refactoring Opportunities/FeatureEnvy.qhelp @@ -0,0 +1,68 @@ + + + + + +

    Feature envy refers to situations where a method is "in the wrong place", because +it does not use many methods or variables of its own class, but uses a whole range of methods or variables from +some other class. This violates the principle of putting data and behavior in the +same place, and exposes internals of the other class to the method.

    + +
    + + +

    For each method that may exhibit feature envy, see if it needs to be declared in +its present location, or if you can move it to the class it is "envious" of. +A common example is a method that calls a large number of getters on +another class to perform a calculation that does not rely on +anything from its own class. In such cases, you should move the method to the class containing the data. +If the calculation depends on some values from the method's current class, they can either be passed as +arguments or accessed using getters from the other class.

    + +

    If it is inappropriate to move the entire method, see if all the dependencies +on the other class are concentrated in just one part of the method. If so, you can move them into a method +of their own. You can then move this method to the other class and call it from the original method.

    + +

    If a class is envious of functionality defined in a superclass, perhaps the +superclass needs to be rewritten to become more extensible and allow its subtypes to +define new behavior without them depending so deeply on the superclass's implementation. The +template method pattern may be useful in achieving this.

    + +

    Modern IDEs provide several refactorings that may be useful in addressing +instances of feature envy, typically under the names of "Move method" and "Extract +method".

    + +

    Occasionally, behavior can be misinterpreted as feature envy when in fact it is justified. The +most common examples are complex design patterns like visitor or strategy, where +the goal is to separate data from behavior. + +

    + +

    In the following example, initially the method getTotalPrice is in the +Basket class, but it only uses data belonging to the Item class. +Therefore, it represents an instance of feature envy. To refactor +it, getTotalPrice can be moved to Item and its +parameter can be removed. The resulting +code is easier to understand and keep consistent.

    + + + +

    The refactored code is still appropriate, even if some data from the Basket class is necessary for the computation of the total price. +For example, if the Basket class applies a bulk discount when a sufficient number of items are +in the basket, an "additional discount" parameter can be added to +Item.getTotalPrice(..). Alternatively, the application of +the discount can be performed in a method in Basket that calls +Item.getTotalPrice.

    + +
    + + +
  • E. Gamma, R. Helm, R. Johnson, J. Vlissides, +Design patterns: elements of reusable object-oriented software. +Addison-Wesley Longman Publishing Co., Inc., Boston, MA, 1995.
  • +
  • W. C. Wake, Refactoring Workbook, pp. 93–94. Addison-Wesley Professional, 2004. +
  • +
    +
    diff --git a/java/ql/src/Architecture/Refactoring Opportunities/FeatureEnvy.ql b/java/ql/src/Architecture/Refactoring Opportunities/FeatureEnvy.ql new file mode 100644 index 00000000000..cfcdda20834 --- /dev/null +++ b/java/ql/src/Architecture/Refactoring Opportunities/FeatureEnvy.ql @@ -0,0 +1,74 @@ +/** + * @name Feature envy + * @description A method that uses more methods or variables from another (unrelated) class than + * from its own class violates the principle of putting data and behavior in the same + * place. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/feature-envy + * @tags maintainability + * modularity + */ +import java + +Member getAUsedMember(Method m) { + result.(Field).getAnAccess().getEnclosingCallable() = m or + result.(Callable).getAReference().getEnclosingCallable() = m +} + +int dependencyCount(Method source, RefType target) { + result = strictcount(Member m | m = getAUsedMember(source) and m = target.getAMember()) +} + +predicate methodDependsOn(Method m, RefType target) { + exists(dependencyCount(m, target)) +} + +predicate dependsOn(RefType source, RefType target) { + methodDependsOn(source.getACallable(), target) +} + +int selfDependencyCount(Method source) { + result = sum(dependencyCount(source, source.getDeclaringType().getEnclosingType*())) +} + +predicate dependsHighlyOn(Method source, RefType target, int selfCount, int depCount) { + depCount = dependencyCount(source, target) and + selfCount = selfDependencyCount(source) and + depCount > 2*selfCount and + depCount > 4 +} + +predicate query(Method m, RefType targetType, int selfCount, int depCount) { + exists(RefType sourceType | sourceType = m.getDeclaringType() | + dependsHighlyOn(m, targetType, selfCount, depCount) and + // Interfaces are depended upon by their very nature + not targetType instanceof Interface and + // Anonymous classes are often used as callbacks, which heavily depend on other classes + not sourceType instanceof AnonymousClass and + // Do not move initializer methods + not m instanceof InitializerMethod and + // Do not move up/down the class hierarchy + not ( + sourceType.getASupertype*().getSourceDeclaration() = targetType or + targetType.getASupertype*().getSourceDeclaration() = sourceType + ) and + // Do not move between nested types + not (sourceType.getEnclosingType*() = targetType or targetType.getEnclosingType*() = sourceType) and + // Tests are allowed to be invasive and depend on the tested classes highly + not sourceType instanceof TestClass and + // Check that the target type already depends on every type used by the method + forall(RefType dependency | methodDependsOn(m, dependency) | dependsOn(targetType, dependency)) + ) +} + +from Method m, RefType other, int selfCount, int depCount +where + query(m, other, selfCount, depCount) and + // Don't include types that are used from many different places - we only highlight + // relatively local fixes that could reasonably be implemented. + count(Method yetAnotherMethod | query(yetAnotherMethod, other, _, _)) < 10 +select m, "Method " + m.getName() + " is too closely tied to $@: " + depCount + + " dependencies to it, but only " + selfCount + " dependencies to its own type.", + other, other.getName() diff --git a/java/ql/src/Architecture/Refactoring Opportunities/HubClasses.qhelp b/java/ql/src/Architecture/Refactoring Opportunities/HubClasses.qhelp new file mode 100644 index 00000000000..7142cb09d01 --- /dev/null +++ b/java/ql/src/Architecture/Refactoring Opportunities/HubClasses.qhelp @@ -0,0 +1,53 @@ + + + + + +

    A hub class is a class that depends on many other classes, and on which many other classes +depend.

    + +

    For the purposes of this rule, a dependency is any use of one class in another. +Examples include:

    + +
      +
    • Using another class as the declared type of a variable or field
    • +
    • Using another class as an argument type for a method
    • +
    • Using another class as a superclass in the extends declaration
    • +
    • Calling a method defined in the class
    • +
    + +

    A class can be regarded as a hub class when both the incoming dependencies and the outgoing +source dependencies are particularly high. (Outgoing source dependencies are dependencies on other +source classes, rather than library classes like java.lang.Object.)

    + +

    It is undesirable to have many hub classes because they are extremely difficult to maintain. This is +because many other classes depend on a hub class, and so the other classes have to be tested and +possibly adapted after each change to the hub class. Also, when one of a hub class's direct +dependencies changes, the behavior of the hub class and all of its dependencies has to be +checked and possibly adapted.

    + +
    + + +

    One common reason for a class to be regarded as a hub class is that it tries to do too much, +including unrelated functionality that depends on different parts of the code base. If +possible, split such classes into several better encapsulated classes.

    + +

    Another common reason is that the class is a "struct-like" class that has many fields of different +types. Introducing some intermediate grouping containers to make it clearer what fields +belong together may be a good option.

    + +
    + + + +
  • E. Gamma, R. Helm, R. Johnson, J. Vlissides, +Design patterns: elements of reusable object-oriented software. +Addison-Wesley Longman Publishing Co., Inc., Boston, MA, 1995.
  • +
  • W. C. Wake, Refactoring Workbook. Addison-Wesley Professional, 2004.
  • + + +
    +
    diff --git a/java/ql/src/Architecture/Refactoring Opportunities/HubClasses.ql b/java/ql/src/Architecture/Refactoring Opportunities/HubClasses.ql new file mode 100644 index 00000000000..dedb0d82649 --- /dev/null +++ b/java/ql/src/Architecture/Refactoring Opportunities/HubClasses.ql @@ -0,0 +1,21 @@ +/** + * @name Hub classes + * @description Hub classes, which are classes that use, and are used by, many other classes, are + * complex and difficult to change without affecting the rest of the system. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/hub-class + * @tags maintainability + * modularity + */ +import java + +from RefType t, int aff, int eff +where + t.fromSource() and + aff = t.getMetrics().getAfferentCoupling() and + eff = t.getMetrics().getEfferentSourceCoupling() and + aff > 15 and eff > 15 +select t as Class, + "Hub class: this class depends on " + eff.toString() + " classes and is used by " + aff.toString() + " classes." diff --git a/java/ql/src/Architecture/Refactoring Opportunities/InappropriateIntimacy.qhelp b/java/ql/src/Architecture/Refactoring Opportunities/InappropriateIntimacy.qhelp new file mode 100644 index 00000000000..54b89dbc263 --- /dev/null +++ b/java/ql/src/Architecture/Refactoring Opportunities/InappropriateIntimacy.qhelp @@ -0,0 +1,40 @@ + + + + + +

    Inappropriate intimacy is an anti-pattern that describes a pair of otherwise unrelated classes +that are too tightly coupled: each class uses a significant number of methods and fields of +the other. This makes both classes difficult to maintain, change and understand. Inappropriate +intimacy is the same as the "feature envy" anti-pattern but in both directions: each class is +"envious" of some functionality or data defined in the other class.

    + +
    + + +

    The solution might be as simple as moving some misplaced methods to their rightful place, or +perhaps some tangled bits of code need to be extracted to their own methods first before being moved.

    + +

    Sometimes the entangled parts (both fields and methods) indicate a +missing object or level of abstraction. It might make sense to combine them into a new +type that can be used in both classes. You may need to introduce delegation to +hide some implementation details.

    + +

    It may be necessary to convert the bidirectional association into a +unidirectional relationship, possibly by using dependency inversion.

    + +

    Modern IDEs provide refactoring support for this sort of issue, usually +with the names "Move method", "Extract method" or "Extract class".

    + +
    + + +
  • E. Gamma, R. Helm, R. Johnson, J. Vlissides, +Design patterns: elements of reusable object-oriented software. +Addison-Wesley Longman Publishing Co., Inc., Boston, MA, 1995.
  • +
  • W. C. Wake, Refactoring Workbook, pp. 95–96. Addison-Wesley Professional, 2004.
  • + +
    +
    diff --git a/java/ql/src/Architecture/Refactoring Opportunities/InappropriateIntimacy.ql b/java/ql/src/Architecture/Refactoring Opportunities/InappropriateIntimacy.ql new file mode 100644 index 00000000000..d62212f9318 --- /dev/null +++ b/java/ql/src/Architecture/Refactoring Opportunities/InappropriateIntimacy.ql @@ -0,0 +1,64 @@ +/** + * @name Inappropriate Intimacy + * @description Two otherwise unrelated classes that share too much information about each other are + * difficult to maintain, change and understand. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/coupled-types + * @tags maintainability + * modularity + */ +import java + +predicate enclosingRefType(Variable v, RefType type) { + v.(Field).getDeclaringType() = type or + v.(LocalVariableDecl).getCallable().getDeclaringType() = type or + v.(Parameter).getCallable().getDeclaringType() = type +} + +predicate remoteVarAccess(RefType source, RefType target, VarAccess va) { + va.getEnclosingCallable().getDeclaringType() = source and + enclosingRefType(va.getVariable(), target) and + source != target +} + +predicate remoteFunAccess(RefType source, RefType target, MethodAccess fc) { + fc.getEnclosingCallable().getDeclaringType() = source and + fc.getMethod().getDeclaringType() = target and + source != target +} + +predicate candidateTypePair(RefType source, RefType target) { + remoteVarAccess(source, target, _) or remoteFunAccess(source, target, _) +} + +predicate variableDependencyCount(RefType source, RefType target, int res) { + candidateTypePair(source, target) and + res = count(VarAccess va | remoteVarAccess(source, target, va)) +} + +predicate functionDependencyCount(RefType source, RefType target, int res) { + candidateTypePair(source, target) and + res = count(MethodAccess fc | remoteFunAccess(source, target, fc)) +} + +predicate dependencyCount(RefType source, RefType target, int res) { + exists(int varCount, int funCount | + variableDependencyCount(source, target, varCount) and + functionDependencyCount(source, target, funCount) and + res = varCount + funCount and + res > 20 + ) +} + +from RefType a, RefType b, int ca, int cb +where + dependencyCount(a, b, ca) and + dependencyCount(b, a, cb) and + ca > 20 and + cb > 20 and + ca >= cb and + not exists(CompilationUnit cu | cu = a.getCompilationUnit() and cu = b.getCompilationUnit()) +select a, "Type " + a.getName() + " is too closely tied to $@ (" + ca.toString() + + " dependencies one way and " + cb.toString() + " the other).", b, b.getName() diff --git a/java/ql/src/Compatibility/JDK9/JdkInternalAccess.qhelp b/java/ql/src/Compatibility/JDK9/JdkInternalAccess.qhelp new file mode 100644 index 00000000000..425e5f64690 --- /dev/null +++ b/java/ql/src/Compatibility/JDK9/JdkInternalAccess.qhelp @@ -0,0 +1,36 @@ + + + + + +

    +Java 9 removes access to various unsupported JDK-internal APIs by default. +

    + +
    + +

    +Examine the use of unsupported JDK-internal APIs and consider replacing them with supported APIs +as recommended in the references. +

    + +
    + + + +
  • +Oracle JDK Documentation: +Oracle JDK 9 Migration Guide. +
  • +
  • +OpenJDK Documentation: +Java Dependency Analysis Tool, +JEP 260: Encapsulate Most Internal APIs, +JEP 261: Module System. +
  • + + +
    +
    diff --git a/java/ql/src/Compatibility/JDK9/JdkInternalAccess.ql b/java/ql/src/Compatibility/JDK9/JdkInternalAccess.ql new file mode 100644 index 00000000000..5fdd1962901 --- /dev/null +++ b/java/ql/src/Compatibility/JDK9/JdkInternalAccess.ql @@ -0,0 +1,121 @@ +/** + * @name Access to unsupported JDK-internal API + * @description Use of unsupported JDK-internal APIs may cause compatibility issues + * when upgrading to newer versions of Java, in particular Java 9. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/jdk-internal-api-access + * @tags maintainability + */ +import java +import JdkInternals +import JdkInternalsReplacement + +predicate importedType(Import i, RefType t) { + i.(ImportType).getImportedType() = t or + i.(ImportStaticTypeMember).getTypeHoldingImport() = t or + i.(ImportStaticOnDemand).getTypeHoldingImport() = t or + i.(ImportOnDemandFromType).getTypeHoldingImport() = t +} + +predicate importedPackage(Import i, Package p) { + i.(ImportOnDemandFromPackage).getPackageHoldingImport() = p +} + +predicate typeReplacement(RefType t, string repl) { + exists(string old | jdkInternalReplacement(old, repl) | + t.getQualifiedName() = old + ) +} + +predicate packageReplacementForType(RefType t, string repl) { + exists(string old, string pkgName | + jdkInternalReplacement(old, repl) and t.getPackage().getName() = pkgName + | + pkgName = old or + pkgName.prefix(old.length()+1) = old + "." + ) +} + +predicate packageReplacement(Package p, string repl) { + exists(string old | jdkInternalReplacement(old, repl) | + p.getName() = old or + p.getName().prefix(old.length()+1) = old + "." + ) +} + +predicate replacement(RefType t, string repl) { + typeReplacement(t, repl) or + not typeReplacement(t, _) and packageReplacementForType(t, repl) +} + +abstract class JdkInternalAccess extends Element { + abstract string getAccessedApi(); + abstract string getReplacement(); +} + +class JdkInternalTypeAccess extends JdkInternalAccess, TypeAccess { + JdkInternalTypeAccess() { + jdkInternalApi(this.getType().(RefType).getPackage().getName()) + } + override string getAccessedApi() { + result = getType().(RefType).getQualifiedName() + } + override string getReplacement() { + exists(RefType t | this.getType() = t | + (replacement(t, result) or not replacement(t, _) and result = "unknown") + ) + } +} + +class JdkInternalImport extends JdkInternalAccess, Import { + JdkInternalImport() { + exists(RefType t | importedType(this, t) | + jdkInternalApi(t.getPackage().getName()) + ) or + exists(Package p | importedPackage(this, p) | + jdkInternalApi(p.getName()) + ) + } + override string getAccessedApi() { + exists(RefType t | result = t.getQualifiedName() | importedType(this, t)) or + exists(Package p | result = p.getName() | importedPackage(this, p)) + } + override string getReplacement() { + exists(RefType t | + importedType(this, t) and + (replacement(t, result) or not replacement(t, _) and result = "unknown") + ) or + exists(Package p | + importedPackage(this, p) and + (packageReplacement(p, result) or not packageReplacement(p, _) and result = "unknown") + ) + } +} + +predicate jdkPackage(Package p) { + exists(string pkgName | + p.getName() = pkgName or + p.getName().prefix(pkgName.length()+1) = pkgName + "." + | + pkgName = "com.sun" or + pkgName = "sun" or + pkgName = "java" or + pkgName = "javax" or + pkgName = "com.oracle.net" or + pkgName = "genstubs" or + pkgName = "jdk" or + pkgName = "build.tools" or + pkgName = "org.omg.CORBA" or + pkgName = "org.ietf.jgss" + ) +} + +from JdkInternalAccess ta, string repl, string msg +where + repl = ta.getReplacement() and + (if (repl="unknown") then msg = "" else msg = " (" + repl + ")") and + not jdkInternalApi(ta.getCompilationUnit().getPackage().getName()) and + not jdkPackage(ta.getCompilationUnit().getPackage()) +select ta, "Access to unsupported JDK-internal API '" + ta.getAccessedApi() + "'." + msg diff --git a/java/ql/src/Compatibility/JDK9/JdkInternals.qll b/java/ql/src/Compatibility/JDK9/JdkInternals.qll new file mode 100644 index 00000000000..bad65751d1b --- /dev/null +++ b/java/ql/src/Compatibility/JDK9/JdkInternals.qll @@ -0,0 +1,1036 @@ +/** + * Provides a QL encoding of the list of unsupported JDK-internal APIs at: + * + * http://hg.openjdk.java.net/jdk9/jdk9/langtools/file/6ba2130e87bd/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/resources/jdk8_internals.txt + */ + +predicate jdkInternalApi(string p) { + p = "apple.applescript" or + p = "apple.laf" or + p = "apple.launcher" or + p = "apple.security" or + p = "com.apple.concurrent" or + p = "com.apple.eawt" or + p = "com.apple.eawt.event" or + p = "com.apple.eio" or + p = "com.apple.laf" or + p = "com.apple.laf.resources" or + p = "com.oracle.jrockit.jfr" or + p = "com.oracle.jrockit.jfr.client" or + p = "com.oracle.jrockit.jfr.management" or + p = "com.oracle.security.ucrypto" or + p = "com.oracle.util" or + p = "com.oracle.webservices.internal.api" or + p = "com.oracle.webservices.internal.api.databinding" or + p = "com.oracle.webservices.internal.api.message" or + p = "com.oracle.webservices.internal.impl.encoding" or + p = "com.oracle.webservices.internal.impl.internalspi.encoding" or + p = "com.oracle.xmlns.internal.webservices.jaxws_databinding" or + p = "com.sun.accessibility.internal.resources" or + p = "com.sun.activation.registries" or + p = "com.sun.awt" or + p = "com.sun.beans" or + p = "com.sun.beans.decoder" or + p = "com.sun.beans.editors" or + p = "com.sun.beans.finder" or + p = "com.sun.beans.infos" or + p = "com.sun.beans.util" or + p = "com.sun.codemodel.internal" or + p = "com.sun.codemodel.internal.fmt" or + p = "com.sun.codemodel.internal.util" or + p = "com.sun.codemodel.internal.writer" or + p = "com.sun.corba.se.impl.activation" or + p = "com.sun.corba.se.impl.copyobject" or + p = "com.sun.corba.se.impl.corba" or + p = "com.sun.corba.se.impl.dynamicany" or + p = "com.sun.corba.se.impl.encoding" or + p = "com.sun.corba.se.impl.interceptors" or + p = "com.sun.corba.se.impl.io" or + p = "com.sun.corba.se.impl.ior" or + p = "com.sun.corba.se.impl.ior.iiop" or + p = "com.sun.corba.se.impl.javax.rmi" or + p = "com.sun.corba.se.impl.javax.rmi.CORBA" or + p = "com.sun.corba.se.impl.legacy.connection" or + p = "com.sun.corba.se.impl.logging" or + p = "com.sun.corba.se.impl.monitoring" or + p = "com.sun.corba.se.impl.naming.cosnaming" or + p = "com.sun.corba.se.impl.naming.namingutil" or + p = "com.sun.corba.se.impl.naming.pcosnaming" or + p = "com.sun.corba.se.impl.oa" or + p = "com.sun.corba.se.impl.oa.poa" or + p = "com.sun.corba.se.impl.oa.toa" or + p = "com.sun.corba.se.impl.orb" or + p = "com.sun.corba.se.impl.orbutil" or + p = "com.sun.corba.se.impl.orbutil.closure" or + p = "com.sun.corba.se.impl.orbutil.concurrent" or + p = "com.sun.corba.se.impl.orbutil.fsm" or + p = "com.sun.corba.se.impl.orbutil.graph" or + p = "com.sun.corba.se.impl.orbutil.threadpool" or + p = "com.sun.corba.se.impl.presentation.rmi" or + p = "com.sun.corba.se.impl.protocol" or + p = "com.sun.corba.se.impl.protocol.giopmsgheaders" or + p = "com.sun.corba.se.impl.resolver" or + p = "com.sun.corba.se.impl.transport" or + p = "com.sun.corba.se.impl.util" or + p = "com.sun.corba.se.internal.CosNaming" or + p = "com.sun.corba.se.internal.Interceptors" or + p = "com.sun.corba.se.internal.POA" or + p = "com.sun.corba.se.internal.corba" or + p = "com.sun.corba.se.internal.iiop" or + p = "com.sun.corba.se.org.omg.CORBA" or + p = "com.sun.corba.se.pept.broker" or + p = "com.sun.corba.se.pept.encoding" or + p = "com.sun.corba.se.pept.protocol" or + p = "com.sun.corba.se.pept.transport" or + p = "com.sun.corba.se.spi.activation" or + p = "com.sun.corba.se.spi.activation.InitialNameServicePackage" or + p = "com.sun.corba.se.spi.activation.LocatorPackage" or + p = "com.sun.corba.se.spi.activation.RepositoryPackage" or + p = "com.sun.corba.se.spi.copyobject" or + p = "com.sun.corba.se.spi.encoding" or + p = "com.sun.corba.se.spi.extension" or + p = "com.sun.corba.se.spi.ior" or + p = "com.sun.corba.se.spi.ior.iiop" or + p = "com.sun.corba.se.spi.legacy.connection" or + p = "com.sun.corba.se.spi.legacy.interceptor" or + p = "com.sun.corba.se.spi.logging" or + p = "com.sun.corba.se.spi.monitoring" or + p = "com.sun.corba.se.spi.oa" or + p = "com.sun.corba.se.spi.orb" or + p = "com.sun.corba.se.spi.orbutil.closure" or + p = "com.sun.corba.se.spi.orbutil.fsm" or + p = "com.sun.corba.se.spi.orbutil.proxy" or + p = "com.sun.corba.se.spi.orbutil.threadpool" or + p = "com.sun.corba.se.spi.presentation.rmi" or + p = "com.sun.corba.se.spi.protocol" or + p = "com.sun.corba.se.spi.resolver" or + p = "com.sun.corba.se.spi.servicecontext" or + p = "com.sun.corba.se.spi.transport" or + p = "com.sun.crypto.provider" or + p = "com.sun.demo.jvmti.hprof" or + p = "com.sun.deploy.uitoolkit.impl.fx" or + p = "com.sun.deploy.uitoolkit.impl.fx.ui" or + p = "com.sun.deploy.uitoolkit.impl.fx.ui.resources" or + p = "com.sun.glass.events" or + p = "com.sun.glass.events.mac" or + p = "com.sun.glass.ui" or + p = "com.sun.glass.ui.delegate" or + p = "com.sun.glass.ui.gtk" or + p = "com.sun.glass.ui.mac" or + p = "com.sun.glass.ui.win" or + p = "com.sun.glass.utils" or + p = "com.sun.image.codec.jpeg" or + p = "com.sun.imageio.plugins.bmp" or + p = "com.sun.imageio.plugins.common" or + p = "com.sun.imageio.plugins.gif" or + p = "com.sun.imageio.plugins.jpeg" or + p = "com.sun.imageio.plugins.png" or + p = "com.sun.imageio.plugins.wbmp" or + p = "com.sun.imageio.spi" or + p = "com.sun.imageio.stream" or + p = "com.sun.istack.internal" or + p = "com.sun.istack.internal.localization" or + p = "com.sun.istack.internal.logging" or + p = "com.sun.istack.internal.tools" or + p = "com.sun.java.accessibility" or + p = "com.sun.java.accessibility.util.java.awt" or + p = "com.sun.java.browser.dom" or + p = "com.sun.java.browser.net" or + p = "com.sun.java.swing" or + p = "com.sun.java.swing.plaf.gtk" or + p = "com.sun.java.swing.plaf.gtk.resources" or + p = "com.sun.java.swing.plaf.motif" or + p = "com.sun.java.swing.plaf.motif.resources" or + p = "com.sun.java.swing.plaf.nimbus" or + p = "com.sun.java.swing.plaf.windows" or + p = "com.sun.java.swing.plaf.windows.resources" or + p = "com.sun.java.util.jar.pack" or + p = "com.sun.java_cup.internal.runtime" or + p = "com.sun.javafx" or + p = "com.sun.javafx.animation" or + p = "com.sun.javafx.applet" or + p = "com.sun.javafx.application" or + p = "com.sun.javafx.beans" or + p = "com.sun.javafx.beans.event" or + p = "com.sun.javafx.binding" or + p = "com.sun.javafx.charts" or + p = "com.sun.javafx.collections" or + p = "com.sun.javafx.css" or + p = "com.sun.javafx.css.converters" or + p = "com.sun.javafx.css.parser" or + p = "com.sun.javafx.cursor" or + p = "com.sun.javafx.effect" or + p = "com.sun.javafx.embed" or + p = "com.sun.javafx.event" or + p = "com.sun.javafx.font" or + p = "com.sun.javafx.font.coretext" or + p = "com.sun.javafx.font.directwrite" or + p = "com.sun.javafx.font.freetype" or + p = "com.sun.javafx.font.t2k" or + p = "com.sun.javafx.fxml" or + p = "com.sun.javafx.fxml.builder" or + p = "com.sun.javafx.fxml.expression" or + p = "com.sun.javafx.geom" or + p = "com.sun.javafx.geom.transform" or + p = "com.sun.javafx.geometry" or + p = "com.sun.javafx.iio" or + p = "com.sun.javafx.iio.bmp" or + p = "com.sun.javafx.iio.common" or + p = "com.sun.javafx.iio.gif" or + p = "com.sun.javafx.iio.ios" or + p = "com.sun.javafx.iio.jpeg" or + p = "com.sun.javafx.iio.png" or + p = "com.sun.javafx.image" or + p = "com.sun.javafx.image.impl" or + p = "com.sun.javafx.jmx" or + p = "com.sun.javafx.logging" or + p = "com.sun.javafx.media" or + p = "com.sun.javafx.menu" or + p = "com.sun.javafx.perf" or + p = "com.sun.javafx.print" or + p = "com.sun.javafx.property" or + p = "com.sun.javafx.property.adapter" or + p = "com.sun.javafx.robot" or + p = "com.sun.javafx.robot.impl" or + p = "com.sun.javafx.runtime" or + p = "com.sun.javafx.runtime.async" or + p = "com.sun.javafx.runtime.eula" or + p = "com.sun.javafx.scene" or + p = "com.sun.javafx.scene.control" or + p = "com.sun.javafx.scene.control.behavior" or + p = "com.sun.javafx.scene.control.skin" or + p = "com.sun.javafx.scene.control.skin.resources" or + p = "com.sun.javafx.scene.input" or + p = "com.sun.javafx.scene.layout.region" or + p = "com.sun.javafx.scene.paint" or + p = "com.sun.javafx.scene.shape" or + p = "com.sun.javafx.scene.text" or + p = "com.sun.javafx.scene.transform" or + p = "com.sun.javafx.scene.traversal" or + p = "com.sun.javafx.scene.web" or + p = "com.sun.javafx.scene.web.behavior" or + p = "com.sun.javafx.scene.web.skin" or + p = "com.sun.javafx.sg.prism" or + p = "com.sun.javafx.sg.prism.web" or + p = "com.sun.javafx.stage" or + p = "com.sun.javafx.text" or + p = "com.sun.javafx.tk" or + p = "com.sun.javafx.tk.quantum" or + p = "com.sun.javafx.util" or + p = "com.sun.javafx.webkit" or + p = "com.sun.javafx.webkit.drt" or + p = "com.sun.javafx.webkit.prism" or + p = "com.sun.javafx.webkit.prism.theme" or + p = "com.sun.javafx.webkit.theme" or + p = "com.sun.jmx.defaults" or + p = "com.sun.jmx.interceptor" or + p = "com.sun.jmx.mbeanserver" or + p = "com.sun.jmx.remote.internal" or + p = "com.sun.jmx.remote.protocol.iiop" or + p = "com.sun.jmx.remote.protocol.rmi" or + p = "com.sun.jmx.remote.security" or + p = "com.sun.jmx.remote.util" or + p = "com.sun.jmx.snmp" or + p = "com.sun.jmx.snmp.IPAcl" or + p = "com.sun.jmx.snmp.agent" or + p = "com.sun.jmx.snmp.daemon" or + p = "com.sun.jmx.snmp.defaults" or + p = "com.sun.jmx.snmp.internal" or + p = "com.sun.jmx.snmp.mpm" or + p = "com.sun.jmx.snmp.tasks" or + p = "com.sun.jndi.cosnaming" or + p = "com.sun.jndi.dns" or + p = "com.sun.jndi.ldap" or + p = "com.sun.jndi.ldap.ext" or + p = "com.sun.jndi.ldap.pool" or + p = "com.sun.jndi.ldap.sasl" or + p = "com.sun.jndi.rmi.registry" or + p = "com.sun.jndi.toolkit.corba" or + p = "com.sun.jndi.toolkit.ctx" or + p = "com.sun.jndi.toolkit.dir" or + p = "com.sun.jndi.toolkit.url" or + p = "com.sun.jndi.url.corbaname" or + p = "com.sun.jndi.url.dns" or + p = "com.sun.jndi.url.iiop" or + p = "com.sun.jndi.url.iiopname" or + p = "com.sun.jndi.url.ldap" or + p = "com.sun.jndi.url.ldaps" or + p = "com.sun.jndi.url.rmi" or + p = "com.sun.management.jmx" or + p = "com.sun.media.jfxmedia" or + p = "com.sun.media.jfxmedia.control" or + p = "com.sun.media.jfxmedia.effects" or + p = "com.sun.media.jfxmedia.events" or + p = "com.sun.media.jfxmedia.locator" or + p = "com.sun.media.jfxmedia.logging" or + p = "com.sun.media.jfxmedia.track" or + p = "com.sun.media.jfxmediaimpl" or + p = "com.sun.media.jfxmediaimpl.platform" or + p = "com.sun.media.jfxmediaimpl.platform.gstreamer" or + p = "com.sun.media.jfxmediaimpl.platform.ios" or + p = "com.sun.media.jfxmediaimpl.platform.java" or + p = "com.sun.media.jfxmediaimpl.platform.osx" or + p = "com.sun.media.sound" or + p = "com.sun.naming.internal" or + p = "com.sun.net.ssl" or + p = "com.sun.net.ssl.internal.ssl" or + p = "com.sun.net.ssl.internal.www.protocol.https" or + p = "com.sun.nio.file" or + p = "com.sun.nio.zipfs" or + p = "com.sun.openpisces" or + p = "com.sun.org.apache.bcel.internal" or + p = "com.sun.org.apache.bcel.internal.classfile" or + p = "com.sun.org.apache.bcel.internal.generic" or + p = "com.sun.org.apache.bcel.internal.util" or + p = "com.sun.org.apache.regexp.internal" or + p = "com.sun.org.apache.xalan.internal" or + p = "com.sun.org.apache.xalan.internal.extensions" or + p = "com.sun.org.apache.xalan.internal.lib" or + p = "com.sun.org.apache.xalan.internal.res" or + p = "com.sun.org.apache.xalan.internal.templates" or + p = "com.sun.org.apache.xalan.internal.utils" or + p = "com.sun.org.apache.xalan.internal.xslt" or + p = "com.sun.org.apache.xalan.internal.xsltc" or + p = "com.sun.org.apache.xalan.internal.xsltc.cmdline" or + p = "com.sun.org.apache.xalan.internal.xsltc.cmdline.getopt" or + p = "com.sun.org.apache.xalan.internal.xsltc.compiler" or + p = "com.sun.org.apache.xalan.internal.xsltc.compiler.util" or + p = "com.sun.org.apache.xalan.internal.xsltc.dom" or + p = "com.sun.org.apache.xalan.internal.xsltc.runtime" or + p = "com.sun.org.apache.xalan.internal.xsltc.runtime.output" or + p = "com.sun.org.apache.xalan.internal.xsltc.trax" or + p = "com.sun.org.apache.xalan.internal.xsltc.util" or + p = "com.sun.org.apache.xerces.internal.dom" or + p = "com.sun.org.apache.xerces.internal.dom.events" or + p = "com.sun.org.apache.xerces.internal.impl" or + p = "com.sun.org.apache.xerces.internal.impl.dtd" or + p = "com.sun.org.apache.xerces.internal.impl.dtd.models" or + p = "com.sun.org.apache.xerces.internal.impl.dv" or + p = "com.sun.org.apache.xerces.internal.impl.dv.dtd" or + p = "com.sun.org.apache.xerces.internal.impl.dv.util" or + p = "com.sun.org.apache.xerces.internal.impl.dv.xs" or + p = "com.sun.org.apache.xerces.internal.impl.io" or + p = "com.sun.org.apache.xerces.internal.impl.msg" or + p = "com.sun.org.apache.xerces.internal.impl.validation" or + p = "com.sun.org.apache.xerces.internal.impl.xpath" or + p = "com.sun.org.apache.xerces.internal.impl.xpath.regex" or + p = "com.sun.org.apache.xerces.internal.impl.xs" or + p = "com.sun.org.apache.xerces.internal.impl.xs.identity" or + p = "com.sun.org.apache.xerces.internal.impl.xs.models" or + p = "com.sun.org.apache.xerces.internal.impl.xs.opti" or + p = "com.sun.org.apache.xerces.internal.impl.xs.traversers" or + p = "com.sun.org.apache.xerces.internal.impl.xs.util" or + p = "com.sun.org.apache.xerces.internal.jaxp" or + p = "com.sun.org.apache.xerces.internal.jaxp.datatype" or + p = "com.sun.org.apache.xerces.internal.jaxp.validation" or + p = "com.sun.org.apache.xerces.internal.parsers" or + p = "com.sun.org.apache.xerces.internal.util" or + p = "com.sun.org.apache.xerces.internal.utils" or + p = "com.sun.org.apache.xerces.internal.xinclude" or + p = "com.sun.org.apache.xerces.internal.xni" or + p = "com.sun.org.apache.xerces.internal.xni.grammars" or + p = "com.sun.org.apache.xerces.internal.xni.parser" or + p = "com.sun.org.apache.xerces.internal.xpointer" or + p = "com.sun.org.apache.xerces.internal.xs" or + p = "com.sun.org.apache.xerces.internal.xs.datatypes" or + p = "com.sun.org.apache.xml.internal.dtm" or + p = "com.sun.org.apache.xml.internal.dtm.ref" or + p = "com.sun.org.apache.xml.internal.dtm.ref.dom2dtm" or + p = "com.sun.org.apache.xml.internal.dtm.ref.sax2dtm" or + p = "com.sun.org.apache.xml.internal.res" or + p = "com.sun.org.apache.xml.internal.resolver" or + p = "com.sun.org.apache.xml.internal.resolver.helpers" or + p = "com.sun.org.apache.xml.internal.resolver.readers" or + p = "com.sun.org.apache.xml.internal.resolver.tools" or + p = "com.sun.org.apache.xml.internal.security" or + p = "com.sun.org.apache.xml.internal.security.algorithms" or + p = "com.sun.org.apache.xml.internal.security.algorithms.implementations" or + p = "com.sun.org.apache.xml.internal.security.c14n" or + p = "com.sun.org.apache.xml.internal.security.c14n.helper" or + p = "com.sun.org.apache.xml.internal.security.c14n.implementations" or + p = "com.sun.org.apache.xml.internal.security.encryption" or + p = "com.sun.org.apache.xml.internal.security.exceptions" or + p = "com.sun.org.apache.xml.internal.security.keys" or + p = "com.sun.org.apache.xml.internal.security.keys.content" or + p = "com.sun.org.apache.xml.internal.security.keys.content.keyvalues" or + p = "com.sun.org.apache.xml.internal.security.keys.content.x509" or + p = "com.sun.org.apache.xml.internal.security.keys.keyresolver" or + p = "com.sun.org.apache.xml.internal.security.keys.keyresolver.implementations" or + p = "com.sun.org.apache.xml.internal.security.keys.storage" or + p = "com.sun.org.apache.xml.internal.security.keys.storage.implementations" or + p = "com.sun.org.apache.xml.internal.security.signature" or + p = "com.sun.org.apache.xml.internal.security.signature.reference" or + p = "com.sun.org.apache.xml.internal.security.transforms" or + p = "com.sun.org.apache.xml.internal.security.transforms.implementations" or + p = "com.sun.org.apache.xml.internal.security.transforms.params" or + p = "com.sun.org.apache.xml.internal.security.utils" or + p = "com.sun.org.apache.xml.internal.security.utils.resolver" or + p = "com.sun.org.apache.xml.internal.security.utils.resolver.implementations" or + p = "com.sun.org.apache.xml.internal.serialize" or + p = "com.sun.org.apache.xml.internal.serializer" or + p = "com.sun.org.apache.xml.internal.serializer.utils" or + p = "com.sun.org.apache.xml.internal.utils" or + p = "com.sun.org.apache.xml.internal.utils.res" or + p = "com.sun.org.apache.xpath.internal" or + p = "com.sun.org.apache.xpath.internal.axes" or + p = "com.sun.org.apache.xpath.internal.compiler" or + p = "com.sun.org.apache.xpath.internal.domapi" or + p = "com.sun.org.apache.xpath.internal.functions" or + p = "com.sun.org.apache.xpath.internal.jaxp" or + p = "com.sun.org.apache.xpath.internal.objects" or + p = "com.sun.org.apache.xpath.internal.operations" or + p = "com.sun.org.apache.xpath.internal.patterns" or + p = "com.sun.org.apache.xpath.internal.res" or + p = "com.sun.org.glassfish.external.amx" or + p = "com.sun.org.glassfish.external.arc" or + p = "com.sun.org.glassfish.external.probe.provider" or + p = "com.sun.org.glassfish.external.probe.provider.annotations" or + p = "com.sun.org.glassfish.external.statistics" or + p = "com.sun.org.glassfish.external.statistics.annotations" or + p = "com.sun.org.glassfish.external.statistics.impl" or + p = "com.sun.org.glassfish.gmbal" or + p = "com.sun.org.glassfish.gmbal.util" or + p = "com.sun.org.omg.CORBA" or + p = "com.sun.org.omg.CORBA.ValueDefPackage" or + p = "com.sun.org.omg.CORBA.portable" or + p = "com.sun.org.omg.SendingContext" or + p = "com.sun.org.omg.SendingContext.CodeBasePackage" or + p = "com.sun.pisces" or + p = "com.sun.prism" or + p = "com.sun.prism.d3d" or + p = "com.sun.prism.es2" or + p = "com.sun.prism.image" or + p = "com.sun.prism.impl" or + p = "com.sun.prism.impl.packrect" or + p = "com.sun.prism.impl.paint" or + p = "com.sun.prism.impl.ps" or + p = "com.sun.prism.impl.shape" or + p = "com.sun.prism.j2d" or + p = "com.sun.prism.j2d.paint" or + p = "com.sun.prism.j2d.print" or + p = "com.sun.prism.paint" or + p = "com.sun.prism.ps" or + p = "com.sun.prism.shader" or + p = "com.sun.prism.shape" or + p = "com.sun.prism.sw" or + p = "com.sun.rmi.rmid" or + p = "com.sun.rowset" or + p = "com.sun.rowset.internal" or + p = "com.sun.rowset.providers" or + p = "com.sun.scenario" or + p = "com.sun.scenario.animation" or + p = "com.sun.scenario.animation.shared" or + p = "com.sun.scenario.effect" or + p = "com.sun.scenario.effect.impl" or + p = "com.sun.scenario.effect.impl.es2" or + p = "com.sun.scenario.effect.impl.hw" or + p = "com.sun.scenario.effect.impl.hw.d3d" or + p = "com.sun.scenario.effect.impl.prism" or + p = "com.sun.scenario.effect.impl.prism.ps" or + p = "com.sun.scenario.effect.impl.prism.sw" or + p = "com.sun.scenario.effect.impl.state" or + p = "com.sun.scenario.effect.impl.sw" or + p = "com.sun.scenario.effect.impl.sw.java" or + p = "com.sun.scenario.effect.impl.sw.sse" or + p = "com.sun.scenario.effect.light" or + p = "com.sun.security.cert.internal.x509" or + p = "com.sun.security.ntlm" or + p = "com.sun.security.sasl" or + p = "com.sun.security.sasl.digest" or + p = "com.sun.security.sasl.gsskerb" or + p = "com.sun.security.sasl.ntlm" or + p = "com.sun.security.sasl.util" or + p = "com.sun.swing.internal.plaf.basic.resources" or + p = "com.sun.swing.internal.plaf.metal.resources" or + p = "com.sun.swing.internal.plaf.synth.resources" or + p = "com.sun.tools.classfile" or + p = "com.sun.tools.corba.se.idl" or + p = "com.sun.tools.corba.se.idl.constExpr" or + p = "com.sun.tools.corba.se.idl.som.cff" or + p = "com.sun.tools.corba.se.idl.som.idlemit" or + p = "com.sun.tools.corba.se.idl.toJavaPortable" or + p = "com.sun.tools.doclets.formats.html" or + p = "com.sun.tools.doclets.formats.html.markup" or + p = "com.sun.tools.doclets.formats.html.resources" or + p = "com.sun.tools.doclets.internal.toolkit" or + p = "com.sun.tools.doclets.internal.toolkit.builders" or + p = "com.sun.tools.doclets.internal.toolkit.resources" or + p = "com.sun.tools.doclets.internal.toolkit.taglets" or + p = "com.sun.tools.doclets.internal.toolkit.util" or + p = "com.sun.tools.doclets.internal.toolkit.util.links" or + p = "com.sun.tools.doclint" or + p = "com.sun.tools.doclint.resources" or + p = "com.sun.tools.example.debug.expr" or + p = "com.sun.tools.example.debug.tty" or + p = "com.sun.tools.extcheck" or + p = "com.sun.tools.hat" or + p = "com.sun.tools.hat.internal.model" or + p = "com.sun.tools.hat.internal.oql" or + p = "com.sun.tools.hat.internal.parser" or + p = "com.sun.tools.hat.internal.server" or + p = "com.sun.tools.hat.internal.util" or + p = "com.sun.tools.internal.jxc" or + p = "com.sun.tools.internal.jxc.ap" or + p = "com.sun.tools.internal.jxc.api" or + p = "com.sun.tools.internal.jxc.api.impl.j2s" or + p = "com.sun.tools.internal.jxc.gen.config" or + p = "com.sun.tools.internal.jxc.model.nav" or + p = "com.sun.tools.internal.ws" or + p = "com.sun.tools.internal.ws.api" or + p = "com.sun.tools.internal.ws.api.wsdl" or + p = "com.sun.tools.internal.ws.processor" or + p = "com.sun.tools.internal.ws.processor.generator" or + p = "com.sun.tools.internal.ws.processor.model" or + p = "com.sun.tools.internal.ws.processor.model.exporter" or + p = "com.sun.tools.internal.ws.processor.model.java" or + p = "com.sun.tools.internal.ws.processor.model.jaxb" or + p = "com.sun.tools.internal.ws.processor.modeler" or + p = "com.sun.tools.internal.ws.processor.modeler.annotation" or + p = "com.sun.tools.internal.ws.processor.modeler.wsdl" or + p = "com.sun.tools.internal.ws.processor.util" or + p = "com.sun.tools.internal.ws.resources" or + p = "com.sun.tools.internal.ws.spi" or + p = "com.sun.tools.internal.ws.util" or + p = "com.sun.tools.internal.ws.util.xml" or + p = "com.sun.tools.internal.ws.wscompile" or + p = "com.sun.tools.internal.ws.wscompile.plugin.at_generated" or + p = "com.sun.tools.internal.ws.wsdl.document" or + p = "com.sun.tools.internal.ws.wsdl.document.http" or + p = "com.sun.tools.internal.ws.wsdl.document.jaxws" or + p = "com.sun.tools.internal.ws.wsdl.document.mime" or + p = "com.sun.tools.internal.ws.wsdl.document.schema" or + p = "com.sun.tools.internal.ws.wsdl.document.soap" or + p = "com.sun.tools.internal.ws.wsdl.framework" or + p = "com.sun.tools.internal.ws.wsdl.parser" or + p = "com.sun.tools.internal.xjc" or + p = "com.sun.tools.internal.xjc.addon.accessors" or + p = "com.sun.tools.internal.xjc.addon.at_generated" or + p = "com.sun.tools.internal.xjc.addon.code_injector" or + p = "com.sun.tools.internal.xjc.addon.episode" or + p = "com.sun.tools.internal.xjc.addon.locator" or + p = "com.sun.tools.internal.xjc.addon.sync" or + p = "com.sun.tools.internal.xjc.api" or + p = "com.sun.tools.internal.xjc.api.impl.s2j" or + p = "com.sun.tools.internal.xjc.api.util" or + p = "com.sun.tools.internal.xjc.generator.annotation.spec" or + p = "com.sun.tools.internal.xjc.generator.bean" or + p = "com.sun.tools.internal.xjc.generator.bean.field" or + p = "com.sun.tools.internal.xjc.generator.util" or + p = "com.sun.tools.internal.xjc.model" or + p = "com.sun.tools.internal.xjc.model.nav" or + p = "com.sun.tools.internal.xjc.outline" or + p = "com.sun.tools.internal.xjc.reader" or + p = "com.sun.tools.internal.xjc.reader.dtd" or + p = "com.sun.tools.internal.xjc.reader.dtd.bindinfo" or + p = "com.sun.tools.internal.xjc.reader.gbind" or + p = "com.sun.tools.internal.xjc.reader.internalizer" or + p = "com.sun.tools.internal.xjc.reader.relaxng" or + p = "com.sun.tools.internal.xjc.reader.xmlschema" or + p = "com.sun.tools.internal.xjc.reader.xmlschema.bindinfo" or + p = "com.sun.tools.internal.xjc.reader.xmlschema.ct" or + p = "com.sun.tools.internal.xjc.reader.xmlschema.parser" or + p = "com.sun.tools.internal.xjc.runtime" or + p = "com.sun.tools.internal.xjc.util" or + p = "com.sun.tools.internal.xjc.writer" or + p = "com.sun.tools.javac.api" or + p = "com.sun.tools.javac.code" or + p = "com.sun.tools.javac.comp" or + p = "com.sun.tools.javac.file" or + p = "com.sun.tools.javac.jvm" or + p = "com.sun.tools.javac.main" or + p = "com.sun.tools.javac.model" or + p = "com.sun.tools.javac.nio" or + p = "com.sun.tools.javac.parser" or + p = "com.sun.tools.javac.processing" or + p = "com.sun.tools.javac.resources" or + p = "com.sun.tools.javac.sym" or + p = "com.sun.tools.javac.tree" or + p = "com.sun.tools.javac.util" or + p = "com.sun.tools.javadoc.api" or + p = "com.sun.tools.javadoc.resources" or + p = "com.sun.tools.javah" or + p = "com.sun.tools.javah.resources" or + p = "com.sun.tools.javap" or + p = "com.sun.tools.javap.resources" or + p = "com.sun.tools.jdeps" or + p = "com.sun.tools.jdeps.resources" or + p = "com.sun.tools.jdi" or + p = "com.sun.tools.jdi.resources" or + p = "com.sun.tools.script.shell" or + p = "com.sun.tracing" or + p = "com.sun.tracing.dtrace" or + p = "com.sun.webkit" or + p = "com.sun.webkit.dom" or + p = "com.sun.webkit.event" or + p = "com.sun.webkit.graphics" or + p = "com.sun.webkit.network" or + p = "com.sun.webkit.network.about" or + p = "com.sun.webkit.network.data" or + p = "com.sun.webkit.perf" or + p = "com.sun.webkit.plugin" or + p = "com.sun.webkit.text" or + p = "com.sun.xml.internal.bind" or + p = "com.sun.xml.internal.bind.annotation" or + p = "com.sun.xml.internal.bind.api" or + p = "com.sun.xml.internal.bind.api.impl" or + p = "com.sun.xml.internal.bind.marshaller" or + p = "com.sun.xml.internal.bind.unmarshaller" or + p = "com.sun.xml.internal.bind.util" or + p = "com.sun.xml.internal.bind.v2" or + p = "com.sun.xml.internal.bind.v2.bytecode" or + p = "com.sun.xml.internal.bind.v2.model.annotation" or + p = "com.sun.xml.internal.bind.v2.model.core" or + p = "com.sun.xml.internal.bind.v2.model.impl" or + p = "com.sun.xml.internal.bind.v2.model.nav" or + p = "com.sun.xml.internal.bind.v2.model.runtime" or + p = "com.sun.xml.internal.bind.v2.model.util" or + p = "com.sun.xml.internal.bind.v2.runtime" or + p = "com.sun.xml.internal.bind.v2.runtime.output" or + p = "com.sun.xml.internal.bind.v2.runtime.property" or + p = "com.sun.xml.internal.bind.v2.runtime.reflect" or + p = "com.sun.xml.internal.bind.v2.runtime.reflect.opt" or + p = "com.sun.xml.internal.bind.v2.runtime.unmarshaller" or + p = "com.sun.xml.internal.bind.v2.schemagen" or + p = "com.sun.xml.internal.bind.v2.schemagen.episode" or + p = "com.sun.xml.internal.bind.v2.schemagen.xmlschema" or + p = "com.sun.xml.internal.bind.v2.util" or + p = "com.sun.xml.internal.dtdparser" or + p = "com.sun.xml.internal.fastinfoset" or + p = "com.sun.xml.internal.fastinfoset.algorithm" or + p = "com.sun.xml.internal.fastinfoset.alphabet" or + p = "com.sun.xml.internal.fastinfoset.dom" or + p = "com.sun.xml.internal.fastinfoset.org.apache.xerces.util" or + p = "com.sun.xml.internal.fastinfoset.sax" or + p = "com.sun.xml.internal.fastinfoset.stax" or + p = "com.sun.xml.internal.fastinfoset.stax.events" or + p = "com.sun.xml.internal.fastinfoset.stax.factory" or + p = "com.sun.xml.internal.fastinfoset.stax.util" or + p = "com.sun.xml.internal.fastinfoset.tools" or + p = "com.sun.xml.internal.fastinfoset.util" or + p = "com.sun.xml.internal.fastinfoset.vocab" or + p = "com.sun.xml.internal.messaging.saaj" or + p = "com.sun.xml.internal.messaging.saaj.client.p2p" or + p = "com.sun.xml.internal.messaging.saaj.packaging.mime" or + p = "com.sun.xml.internal.messaging.saaj.packaging.mime.internet" or + p = "com.sun.xml.internal.messaging.saaj.packaging.mime.util" or + p = "com.sun.xml.internal.messaging.saaj.soap" or + p = "com.sun.xml.internal.messaging.saaj.soap.dynamic" or + p = "com.sun.xml.internal.messaging.saaj.soap.impl" or + p = "com.sun.xml.internal.messaging.saaj.soap.name" or + p = "com.sun.xml.internal.messaging.saaj.soap.ver1_1" or + p = "com.sun.xml.internal.messaging.saaj.soap.ver1_2" or + p = "com.sun.xml.internal.messaging.saaj.util" or + p = "com.sun.xml.internal.messaging.saaj.util.transform" or + p = "com.sun.xml.internal.org.jvnet.fastinfoset" or + p = "com.sun.xml.internal.org.jvnet.fastinfoset.sax" or + p = "com.sun.xml.internal.org.jvnet.fastinfoset.sax.helpers" or + p = "com.sun.xml.internal.org.jvnet.fastinfoset.stax" or + p = "com.sun.xml.internal.org.jvnet.mimepull" or + p = "com.sun.xml.internal.org.jvnet.staxex" or + p = "com.sun.xml.internal.rngom.ast.builder" or + p = "com.sun.xml.internal.rngom.ast.om" or + p = "com.sun.xml.internal.rngom.ast.util" or + p = "com.sun.xml.internal.rngom.binary" or + p = "com.sun.xml.internal.rngom.binary.visitor" or + p = "com.sun.xml.internal.rngom.digested" or + p = "com.sun.xml.internal.rngom.dt" or + p = "com.sun.xml.internal.rngom.dt.builtin" or + p = "com.sun.xml.internal.rngom.nc" or + p = "com.sun.xml.internal.rngom.parse" or + p = "com.sun.xml.internal.rngom.parse.compact" or + p = "com.sun.xml.internal.rngom.parse.host" or + p = "com.sun.xml.internal.rngom.parse.xml" or + p = "com.sun.xml.internal.rngom.util" or + p = "com.sun.xml.internal.rngom.xml.sax" or + p = "com.sun.xml.internal.rngom.xml.util" or + p = "com.sun.xml.internal.stream" or + p = "com.sun.xml.internal.stream.buffer" or + p = "com.sun.xml.internal.stream.buffer.sax" or + p = "com.sun.xml.internal.stream.buffer.stax" or + p = "com.sun.xml.internal.stream.dtd" or + p = "com.sun.xml.internal.stream.dtd.nonvalidating" or + p = "com.sun.xml.internal.stream.events" or + p = "com.sun.xml.internal.stream.util" or + p = "com.sun.xml.internal.stream.writers" or + p = "com.sun.xml.internal.txw2" or + p = "com.sun.xml.internal.txw2.annotation" or + p = "com.sun.xml.internal.txw2.output" or + p = "com.sun.xml.internal.ws" or + p = "com.sun.xml.internal.ws.addressing" or + p = "com.sun.xml.internal.ws.addressing.model" or + p = "com.sun.xml.internal.ws.addressing.policy" or + p = "com.sun.xml.internal.ws.addressing.v200408" or + p = "com.sun.xml.internal.ws.api" or + p = "com.sun.xml.internal.ws.api.addressing" or + p = "com.sun.xml.internal.ws.api.client" or + p = "com.sun.xml.internal.ws.api.config.management" or + p = "com.sun.xml.internal.ws.api.config.management.policy" or + p = "com.sun.xml.internal.ws.api.databinding" or + p = "com.sun.xml.internal.ws.api.fastinfoset" or + p = "com.sun.xml.internal.ws.api.ha" or + p = "com.sun.xml.internal.ws.api.handler" or + p = "com.sun.xml.internal.ws.api.message" or + p = "com.sun.xml.internal.ws.api.message.saaj" or + p = "com.sun.xml.internal.ws.api.message.stream" or + p = "com.sun.xml.internal.ws.api.model" or + p = "com.sun.xml.internal.ws.api.model.soap" or + p = "com.sun.xml.internal.ws.api.model.wsdl" or + p = "com.sun.xml.internal.ws.api.model.wsdl.editable" or + p = "com.sun.xml.internal.ws.api.pipe" or + p = "com.sun.xml.internal.ws.api.pipe.helper" or + p = "com.sun.xml.internal.ws.api.policy" or + p = "com.sun.xml.internal.ws.api.policy.subject" or + p = "com.sun.xml.internal.ws.api.server" or + p = "com.sun.xml.internal.ws.api.streaming" or + p = "com.sun.xml.internal.ws.api.wsdl.parser" or + p = "com.sun.xml.internal.ws.api.wsdl.writer" or + p = "com.sun.xml.internal.ws.assembler" or + p = "com.sun.xml.internal.ws.assembler.dev" or + p = "com.sun.xml.internal.ws.assembler.jaxws" or + p = "com.sun.xml.internal.ws.binding" or + p = "com.sun.xml.internal.ws.client" or + p = "com.sun.xml.internal.ws.client.dispatch" or + p = "com.sun.xml.internal.ws.client.sei" or + p = "com.sun.xml.internal.ws.commons.xmlutil" or + p = "com.sun.xml.internal.ws.config.management.policy" or + p = "com.sun.xml.internal.ws.config.metro.dev" or + p = "com.sun.xml.internal.ws.config.metro.util" or + p = "com.sun.xml.internal.ws.db" or + p = "com.sun.xml.internal.ws.db.glassfish" or + p = "com.sun.xml.internal.ws.developer" or + p = "com.sun.xml.internal.ws.dump" or + p = "com.sun.xml.internal.ws.encoding" or + p = "com.sun.xml.internal.ws.encoding.fastinfoset" or + p = "com.sun.xml.internal.ws.encoding.policy" or + p = "com.sun.xml.internal.ws.encoding.soap" or + p = "com.sun.xml.internal.ws.encoding.soap.streaming" or + p = "com.sun.xml.internal.ws.encoding.xml" or + p = "com.sun.xml.internal.ws.fault" or + p = "com.sun.xml.internal.ws.handler" or + p = "com.sun.xml.internal.ws.message" or + p = "com.sun.xml.internal.ws.message.jaxb" or + p = "com.sun.xml.internal.ws.message.saaj" or + p = "com.sun.xml.internal.ws.message.source" or + p = "com.sun.xml.internal.ws.message.stream" or + p = "com.sun.xml.internal.ws.model" or + p = "com.sun.xml.internal.ws.model.soap" or + p = "com.sun.xml.internal.ws.model.wsdl" or + p = "com.sun.xml.internal.ws.org.objectweb.asm" or + p = "com.sun.xml.internal.ws.policy" or + p = "com.sun.xml.internal.ws.policy.jaxws" or + p = "com.sun.xml.internal.ws.policy.jaxws.spi" or + p = "com.sun.xml.internal.ws.policy.privateutil" or + p = "com.sun.xml.internal.ws.policy.sourcemodel" or + p = "com.sun.xml.internal.ws.policy.sourcemodel.attach" or + p = "com.sun.xml.internal.ws.policy.sourcemodel.wspolicy" or + p = "com.sun.xml.internal.ws.policy.spi" or + p = "com.sun.xml.internal.ws.policy.subject" or + p = "com.sun.xml.internal.ws.protocol.soap" or + p = "com.sun.xml.internal.ws.protocol.xml" or + p = "com.sun.xml.internal.ws.resources" or + p = "com.sun.xml.internal.ws.runtime.config" or + p = "com.sun.xml.internal.ws.server" or + p = "com.sun.xml.internal.ws.server.provider" or + p = "com.sun.xml.internal.ws.server.sei" or + p = "com.sun.xml.internal.ws.spi" or + p = "com.sun.xml.internal.ws.spi.db" or + p = "com.sun.xml.internal.ws.streaming" or + p = "com.sun.xml.internal.ws.transport" or + p = "com.sun.xml.internal.ws.transport.http" or + p = "com.sun.xml.internal.ws.transport.http.client" or + p = "com.sun.xml.internal.ws.transport.http.server" or + p = "com.sun.xml.internal.ws.util" or + p = "com.sun.xml.internal.ws.util.exception" or + p = "com.sun.xml.internal.ws.util.pipe" or + p = "com.sun.xml.internal.ws.util.xml" or + p = "com.sun.xml.internal.ws.wsdl" or + p = "com.sun.xml.internal.ws.wsdl.parser" or + p = "com.sun.xml.internal.ws.wsdl.writer" or + p = "com.sun.xml.internal.ws.wsdl.writer.document" or + p = "com.sun.xml.internal.ws.wsdl.writer.document.http" or + p = "com.sun.xml.internal.ws.wsdl.writer.document.soap" or + p = "com.sun.xml.internal.ws.wsdl.writer.document.soap12" or + p = "com.sun.xml.internal.ws.wsdl.writer.document.xsd" or + p = "com.sun.xml.internal.xsom" or + p = "com.sun.xml.internal.xsom.impl" or + p = "com.sun.xml.internal.xsom.impl.parser" or + p = "com.sun.xml.internal.xsom.impl.parser.state" or + p = "com.sun.xml.internal.xsom.impl.scd" or + p = "com.sun.xml.internal.xsom.impl.util" or + p = "com.sun.xml.internal.xsom.parser" or + p = "com.sun.xml.internal.xsom.util" or + p = "com.sun.xml.internal.xsom.visitor" or + p = "java.awt.dnd.peer" or + p = "java.awt.peer" or + p = "javafx.embed.swt" or + p = "jdk" or + p = "jdk.internal.cmm" or + p = "jdk.internal.dynalink" or + p = "jdk.internal.dynalink.beans" or + p = "jdk.internal.dynalink.linker" or + p = "jdk.internal.dynalink.support" or + p = "jdk.internal.instrumentation" or + p = "jdk.internal.org.objectweb.asm" or + p = "jdk.internal.org.objectweb.asm.commons" or + p = "jdk.internal.org.objectweb.asm.signature" or + p = "jdk.internal.org.objectweb.asm.tree" or + p = "jdk.internal.org.objectweb.asm.tree.analysis" or + p = "jdk.internal.org.objectweb.asm.util" or + p = "jdk.internal.org.xml.sax" or + p = "jdk.internal.org.xml.sax.helpers" or + p = "jdk.internal.util.xml" or + p = "jdk.internal.util.xml.impl" or + p = "jdk.jfr.events" or + p = "jdk.management.resource.internal" or + p = "jdk.management.resource.internal.inst" or + p = "jdk.nashorn.internal" or + p = "jdk.nashorn.internal.codegen" or + p = "jdk.nashorn.internal.codegen.types" or + p = "jdk.nashorn.internal.ir" or + p = "jdk.nashorn.internal.ir.annotations" or + p = "jdk.nashorn.internal.ir.debug" or + p = "jdk.nashorn.internal.ir.visitor" or + p = "jdk.nashorn.internal.lookup" or + p = "jdk.nashorn.internal.objects" or + p = "jdk.nashorn.internal.objects.annotations" or + p = "jdk.nashorn.internal.parser" or + p = "jdk.nashorn.internal.runtime" or + p = "jdk.nashorn.internal.runtime.arrays" or + p = "jdk.nashorn.internal.runtime.events" or + p = "jdk.nashorn.internal.runtime.linker" or + p = "jdk.nashorn.internal.runtime.logging" or + p = "jdk.nashorn.internal.runtime.options" or + p = "jdk.nashorn.internal.runtime.regexp" or + p = "jdk.nashorn.internal.runtime.regexp.joni" or + p = "jdk.nashorn.internal.runtime.regexp.joni.ast" or + p = "jdk.nashorn.internal.runtime.regexp.joni.constants" or + p = "jdk.nashorn.internal.runtime.regexp.joni.encoding" or + p = "jdk.nashorn.internal.runtime.regexp.joni.exception" or + p = "jdk.nashorn.internal.scripts" or + p = "jdk.nashorn.tools" or + p = "oracle.jrockit.jfr" or + p = "oracle.jrockit.jfr.events" or + p = "oracle.jrockit.jfr.jdkevents" or + p = "oracle.jrockit.jfr.jdkevents.throwabletransform" or + p = "oracle.jrockit.jfr.openmbean" or + p = "oracle.jrockit.jfr.parser" or + p = "oracle.jrockit.jfr.settings" or + p = "oracle.jrockit.jfr.tools" or + p = "org.jcp.xml.dsig.internal" or + p = "org.jcp.xml.dsig.internal.dom" or + p = "org.omg.stub.javax.management.remote.rmi" or + p = "org.relaxng.datatype" or + p = "org.relaxng.datatype.helpers" or + p = "sun.applet" or + p = "sun.applet.resources" or + p = "sun.audio" or + p = "sun.awt" or + p = "sun.awt.X11" or + p = "sun.awt.datatransfer" or + p = "sun.awt.dnd" or + p = "sun.awt.event" or + p = "sun.awt.geom" or + p = "sun.awt.im" or + p = "sun.awt.image" or + p = "sun.awt.image.codec" or + p = "sun.awt.motif" or + p = "sun.awt.resources" or + p = "sun.awt.shell" or + p = "sun.awt.util" or + p = "sun.awt.windows" or + p = "sun.corba" or + p = "sun.dc" or + p = "sun.dc.path" or + p = "sun.dc.pr" or + p = "sun.font" or + p = "sun.instrument" or + p = "sun.invoke" or + p = "sun.invoke.anon" or + p = "sun.invoke.empty" or + p = "sun.invoke.util" or + p = "sun.io" or + p = "sun.java2d" or + p = "sun.java2d.cmm" or + p = "sun.java2d.cmm.kcms" or + p = "sun.java2d.cmm.lcms" or + p = "sun.java2d.d3d" or + p = "sun.java2d.jules" or + p = "sun.java2d.loops" or + p = "sun.java2d.opengl" or + p = "sun.java2d.pipe" or + p = "sun.java2d.pipe.hw" or + p = "sun.java2d.pisces" or + p = "sun.java2d.windows" or + p = "sun.java2d.x11" or + p = "sun.java2d.xr" or + p = "sun.jvmstat.monitor" or + p = "sun.jvmstat.monitor.event" or + p = "sun.jvmstat.monitor.remote" or + p = "sun.jvmstat.perfdata.monitor" or + p = "sun.jvmstat.perfdata.monitor.protocol.file" or + p = "sun.jvmstat.perfdata.monitor.protocol.local" or + p = "sun.jvmstat.perfdata.monitor.protocol.rmi" or + p = "sun.jvmstat.perfdata.monitor.v1_0" or + p = "sun.jvmstat.perfdata.monitor.v2_0" or + p = "sun.launcher" or + p = "sun.launcher.resources" or + p = "sun.lwawt" or + p = "sun.lwawt.macosx" or + p = "sun.management" or + p = "sun.management.counter" or + p = "sun.management.counter.perf" or + p = "sun.management.jdp" or + p = "sun.management.jmxremote" or + p = "sun.management.resources" or + p = "sun.management.snmp" or + p = "sun.management.snmp.jvminstr" or + p = "sun.management.snmp.jvmmib" or + p = "sun.management.snmp.util" or + p = "sun.misc" or + p = "sun.misc.resources" or + p = "sun.net" or + p = "sun.net.dns" or + p = "sun.net.ftp" or + p = "sun.net.ftp.impl" or + p = "sun.net.httpserver" or + p = "sun.net.idn" or + p = "sun.net.sdp" or + p = "sun.net.smtp" or + p = "sun.net.spi" or + p = "sun.net.spi.nameservice" or + p = "sun.net.spi.nameservice.dns" or + p = "sun.net.util" or + p = "sun.net.www" or + p = "sun.net.www.content.audio" or + p = "sun.net.www.content.image" or + p = "sun.net.www.content.text" or + p = "sun.net.www.http" or + p = "sun.net.www.protocol.file" or + p = "sun.net.www.protocol.ftp" or + p = "sun.net.www.protocol.http" or + p = "sun.net.www.protocol.http.logging" or + p = "sun.net.www.protocol.http.ntlm" or + p = "sun.net.www.protocol.http.spnego" or + p = "sun.net.www.protocol.https" or + p = "sun.net.www.protocol.jar" or + p = "sun.net.www.protocol.mailto" or + p = "sun.net.www.protocol.netdoc" or + p = "sun.nio" or + p = "sun.nio.ch" or + p = "sun.nio.ch.sctp" or + p = "sun.nio.cs" or + p = "sun.nio.cs.ext" or + p = "sun.nio.fs" or + p = "sun.print" or + p = "sun.print.resources" or + p = "sun.reflect" or + p = "sun.reflect.annotation" or + p = "sun.reflect.generics.factory" or + p = "sun.reflect.generics.parser" or + p = "sun.reflect.generics.reflectiveObjects" or + p = "sun.reflect.generics.repository" or + p = "sun.reflect.generics.scope" or + p = "sun.reflect.generics.tree" or + p = "sun.reflect.generics.visitor" or + p = "sun.reflect.misc" or + p = "sun.rmi.log" or + p = "sun.rmi.registry" or + p = "sun.rmi.rmic" or + p = "sun.rmi.rmic.iiop" or + p = "sun.rmi.rmic.newrmic" or + p = "sun.rmi.rmic.newrmic.jrmp" or + p = "sun.rmi.runtime" or + p = "sun.rmi.server" or + p = "sun.rmi.transport" or + p = "sun.rmi.transport.proxy" or + p = "sun.rmi.transport.tcp" or + p = "sun.security.acl" or + p = "sun.security.action" or + p = "sun.security.ec" or + p = "sun.security.internal.interfaces" or + p = "sun.security.internal.spec" or + p = "sun.security.jca" or + p = "sun.security.jgss" or + p = "sun.security.jgss.krb5" or + p = "sun.security.jgss.spi" or + p = "sun.security.jgss.spnego" or + p = "sun.security.jgss.wrapper" or + p = "sun.security.krb5" or + p = "sun.security.krb5.internal" or + p = "sun.security.krb5.internal.ccache" or + p = "sun.security.krb5.internal.crypto" or + p = "sun.security.krb5.internal.crypto.dk" or + p = "sun.security.krb5.internal.ktab" or + p = "sun.security.krb5.internal.rcache" or + p = "sun.security.krb5.internal.tools" or + p = "sun.security.krb5.internal.util" or + p = "sun.security.mscapi" or + p = "sun.security.pkcs" or + p = "sun.security.pkcs10" or + p = "sun.security.pkcs11" or + p = "sun.security.pkcs11.wrapper" or + p = "sun.security.pkcs12" or + p = "sun.security.provider" or + p = "sun.security.provider.certpath" or + p = "sun.security.provider.certpath.ldap" or + p = "sun.security.provider.certpath.ssl" or + p = "sun.security.rsa" or + p = "sun.security.smartcardio" or + p = "sun.security.ssl" or + p = "sun.security.ssl.krb5" or + p = "sun.security.timestamp" or + p = "sun.security.tools" or + p = "sun.security.tools.jarsigner" or + p = "sun.security.tools.keytool" or + p = "sun.security.tools.policytool" or + p = "sun.security.util" or + p = "sun.security.validator" or + p = "sun.security.x509" or + p = "sun.swing" or + p = "sun.swing.icon" or + p = "sun.swing.plaf" or + p = "sun.swing.plaf.synth" or + p = "sun.swing.plaf.windows" or + p = "sun.swing.table" or + p = "sun.swing.text" or + p = "sun.swing.text.html" or + p = "sun.text" or + p = "sun.text.bidi" or + p = "sun.text.normalizer" or + p = "sun.text.resources" or + p = "sun.text.resources.en" or + p = "sun.tools.asm" or + p = "sun.tools.attach" or + p = "sun.tools.jar" or + p = "sun.tools.jar.resources" or + p = "sun.tools.java" or + p = "sun.tools.javac" or + p = "sun.tools.jcmd" or + p = "sun.tools.jconsole" or + p = "sun.tools.jconsole.inspector" or + p = "sun.tools.jinfo" or + p = "sun.tools.jmap" or + p = "sun.tools.jps" or + p = "sun.tools.jstack" or + p = "sun.tools.jstat" or + p = "sun.tools.jstatd" or + p = "sun.tools.native2ascii" or + p = "sun.tools.native2ascii.resources" or + p = "sun.tools.serialver" or + p = "sun.tools.tree" or + p = "sun.tools.util" or + p = "sun.tracing" or + p = "sun.tracing.dtrace" or + p = "sun.usagetracker" or + p = "sun.util" or + p = "sun.util.calendar" or + p = "sun.util.cldr" or + p = "sun.util.locale" or + p = "sun.util.locale.provider" or + p = "sun.util.logging" or + p = "sun.util.logging.resources" or + p = "sun.util.resources" or + p = "sun.util.resources.en" or + p = "sun.util.spi" or + p = "sun.util.xml" +} diff --git a/java/ql/src/Compatibility/JDK9/JdkInternalsReplacement.qll b/java/ql/src/Compatibility/JDK9/JdkInternalsReplacement.qll new file mode 100644 index 00000000000..8298ce93fca --- /dev/null +++ b/java/ql/src/Compatibility/JDK9/JdkInternalsReplacement.qll @@ -0,0 +1,54 @@ +/** + * Provides a QL encoding of the suggested replacements for unsupported JDK-internal APIs listed at: + * + * http://hg.openjdk.java.net/jdk9/jdk9/langtools/file/6ba2130e87bd/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/resources/jdkinternals.properties + */ + +predicate jdkInternalReplacement(string old, string new) { + exists(string r, int eqIdx | jdkInternalReplacement(r) and eqIdx = r.indexOf("=") | + old = r.prefix(eqIdx) and + new = r.suffix(eqIdx+1) + ) +} + +private +predicate jdkInternalReplacement(string r) { + r = "com.sun.crypto.provider.SunJCE=Use java.security.Security.getProvider(provider-name) @since 1.3" or + r = "com.sun.org.apache.xml.internal.security=Use java.xml.crypto @since 1.6" or + r = "com.sun.org.apache.xml.internal.security.utils.Base64=Use java.util.Base64 @since 1.8" or + r = "com.sun.org.apache.xml.internal.resolver=Use javax.xml.catalog @since 9" or + r = "com.sun.net.ssl=Use javax.net.ssl @since 1.4" or + r = "com.sun.net.ssl.internal.ssl.Provider=Use java.security.Security.getProvider(provider-name) @since 1.3" or + r = "com.sun.rowset=Use javax.sql.rowset.RowSetProvider @since 1.7" or + r = "com.sun.tools.javac.tree=Use com.sun.source @since 1.6" or + r = "com.sun.tools.javac=Use javax.tools and javax.lang.model @since 1.6" or + r = "java.awt.peer=Should not use. See https://bugs.openjdk.java.net/browse/JDK-8037739" or + r = "java.awt.dnd.peer=Should not use. See https://bugs.openjdk.java.net/browse/JDK-8037739" or + r = "jdk.internal.ref.Cleaner=Use java.lang.ref.PhantomReference @since 1.2 or java.lang.ref.Cleaner @since 9" or + r = "sun.awt.CausedFocusEvent=Use java.awt.event.FocusEvent::getCause @since 9" or + r = "sun.font.FontUtilities=See java.awt.Font.textRequiresLayout @since 9" or + r = "sun.reflect.Reflection=Use java.lang.StackWalker @since 9" or + r = "sun.reflect.ReflectionFactory=See http://openjdk.java.net/jeps/260" or + r = "sun.misc.Unsafe=See http://openjdk.java.net/jeps/260" or + r = "sun.misc.Signal=See http://openjdk.java.net/jeps/260" or + r = "sun.misc.SignalHandler=See http://openjdk.java.net/jeps/260" or + r = "sun.security.action=Use java.security.PrivilegedAction @since 1.1" or + r = "sun.security.krb5=Use com.sun.security.jgss" or + r = "sun.security.provider.PolicyFile=Use java.security.Policy.getInstance(\"JavaPolicy\", new URIParameter(uri)) @since 1.6" or + r = "sun.security.provider.Sun=Use java.security.Security.getProvider(provider-name) @since 1.3" or + r = "sun.security.util.HostnameChecker=Use javax.net.ssl.SSLParameters.setEndpointIdentificationAlgorithm(\"HTTPS\") @since 1.7 or javax.net.ssl.HttpsURLConnection.setHostnameVerifier() @since 1.4" or + r = "sun.security.util.SecurityConstants=Use appropriate java.security.Permission subclass @since 1.1" or + r = "sun.security.x509.X500Name=Use javax.security.auth.x500.X500Principal @since 1.4" or + r = "sun.tools.jar=Use java.util.jar or jar tool @since 1.2" or + // Internal APIs removed in JDK 9 + r = "com.apple.eawt=Use java.awt.Desktop and JEP 272 @since 9" or + r = "com.apple.concurrent=Removed. See https://bugs.openjdk.java.net/browse/JDK-8148187" or + r = "com.sun.image.codec.jpeg=Use javax.imageio @since 1.4" or + r = "sun.awt.image.codec=Use javax.imageio @since 1.4" or + r = "sun.misc.BASE64Encoder=Use java.util.Base64 @since 1.8" or + r = "sun.misc.BASE64Decoder=Use java.util.Base64 @since 1.8" or + r = "sun.misc.Cleaner=Use java.lang.ref.PhantomReference @since 1.2 or java.lang.ref.Cleaner @since 9" or + r = "sun.misc.Service=Use java.util.ServiceLoader @since 1.6" or + r = "sun.misc=Removed. See http://openjdk.java.net/jeps/260" or + r = "sun.reflect=Removed. See http://openjdk.java.net/jeps/260" +} diff --git a/java/ql/src/Compatibility/JDK9/UnderscoreIdentifier.qhelp b/java/ql/src/Compatibility/JDK9/UnderscoreIdentifier.qhelp new file mode 100644 index 00000000000..39801c6bc98 --- /dev/null +++ b/java/ql/src/Compatibility/JDK9/UnderscoreIdentifier.qhelp @@ -0,0 +1,34 @@ + + + + + +

    +The underscore character is a reserved keyword in Java 9 and +therefore disallowed as a one-character identifier. +

    + +
    + +

    +Rename any identifiers that consist of a one-character underscore. +

    + +
    + + + +
  • +Oracle JDK Documentation: +Oracle JDK 9 Migration Guide. +
  • +
  • +JDK Bug System: +Disallow _ as a one-character identifier. +
  • + + +
    +
    diff --git a/java/ql/src/Compatibility/JDK9/UnderscoreIdentifier.ql b/java/ql/src/Compatibility/JDK9/UnderscoreIdentifier.ql new file mode 100644 index 00000000000..2fef4eaa528 --- /dev/null +++ b/java/ql/src/Compatibility/JDK9/UnderscoreIdentifier.ql @@ -0,0 +1,33 @@ +/** + * @name Underscore used as identifier + * @description Use of a single underscore character as an identifier + * results in a compiler error with Java source level 9 or later. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/underscore-identifier + * @tags maintainability + */ +import java + +class IdentifierElement extends Element { + IdentifierElement() { + this instanceof CompilationUnit or + this.(RefType).isSourceDeclaration() or + this.(Callable).isSourceDeclaration() or + this instanceof Variable + } +} + +from IdentifierElement e, string msg +where + e.fromSource() and + not e.(Constructor).isDefaultConstructor() and + ( + e.getName() = "_" and + msg = "." + or + e.(CompilationUnit).getPackage().getName().splitAt(".") = "_" and + msg = " in package name '" + e.(CompilationUnit).getPackage().getName() + "'." + ) +select e, "Use of underscore as a one-character identifier" + msg diff --git a/java/ql/src/Complexity/BlockWithTooManyStatements.qhelp b/java/ql/src/Complexity/BlockWithTooManyStatements.qhelp new file mode 100644 index 00000000000..426bc5d5af8 --- /dev/null +++ b/java/ql/src/Complexity/BlockWithTooManyStatements.qhelp @@ -0,0 +1,40 @@ + + + + + +

    Code has a tendency to become more complex over time. +A method that is initially simple may need to be extended to accommodate additional functionality or to +address defects. Before long it becomes unreadable and unmaintainable, with +many complex statements nested within each other.

    + +

    This rule applies to a block that contains +a significant number of complex statements. Note that this +is quite different from just considering the number of statements in a block, because +each complex statement is potentially a candidate for being extracted to a new method as part of refactoring. +For the purposes of this rule, loops and switch statements are considered to be complex.

    + +
    + + +

    To make the code more understandable and less complex, identify logical units and extract them to +new methods. As a result, the top-level logic becomes clearer.

    + +
    + + + +
  • + M. Fowler, Refactoring: Improving the Design of Existing Code. + Addison-Wesley Professional, 1999. +
  • +
  • + W. C. Wake, Refactoring Workbook. + Addison-Wesley Professional, 2004. +
  • + + +
    +
    diff --git a/java/ql/src/Complexity/BlockWithTooManyStatements.ql b/java/ql/src/Complexity/BlockWithTooManyStatements.ql new file mode 100644 index 00000000000..f2719f586d5 --- /dev/null +++ b/java/ql/src/Complexity/BlockWithTooManyStatements.ql @@ -0,0 +1,24 @@ +/** + * @name Block with too many statements + * @description A block that contains too many complex statements becomes unreadable and + * unmaintainable. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/complex-block + * @tags maintainability + * testability + * complexity + */ +import java + +class ComplexStmt extends Stmt { + ComplexStmt() { + this instanceof LoopStmt or + this instanceof SwitchStmt + } +} + +from Block b, int n +where n = count(ComplexStmt s | s = b.getAStmt()) and n > 3 +select b, "Block with too many statements (" + n.toString() + " complex statements in the block)." diff --git a/java/ql/src/Complexity/ComplexCondition.java b/java/ql/src/Complexity/ComplexCondition.java new file mode 100644 index 00000000000..a46d92a4aa9 --- /dev/null +++ b/java/ql/src/Complexity/ComplexCondition.java @@ -0,0 +1,19 @@ +public class Dialog +{ + // ... + + private void validate() { + // TODO: check that this covers all cases + if ((id != null && id.length() == 0) || + ((partner == null || partner.id == -1) && + ((option == Options.SHORT && parameter.length() == 0) || + (option == Options.LONG && parameter.length() < 8)))) + { + disableOKButton(); + } else { + enableOKButton(); + } + } + + // ... +} \ No newline at end of file diff --git a/java/ql/src/Complexity/ComplexCondition.qhelp b/java/ql/src/Complexity/ComplexCondition.qhelp new file mode 100644 index 00000000000..057a3a59875 --- /dev/null +++ b/java/ql/src/Complexity/ComplexCondition.qhelp @@ -0,0 +1,53 @@ + + + + + +

    In general, very complex conditions are difficult to write and read, and increase the chance +of defects.

    + +
    + + +

    Firstly, a condition can often be simplified by changing other parts of the +code to initialize variables more consistently. For example, is +there a semantic difference between id being null and +having zero-length? If not, choosing one sentinel value and using it +consistently simplifies most uses of that variable.

    + +

    Secondly, extracting part of a condition into a Boolean-valued method can simplify the condition +and also allow code reuse, with all its benefits.

    + +

    Thirdly, assigning each subcondition of the condition to a local variable, and then using the variables in +the condition instead can simplify the condition.

    + +
    + + +

    The following example shows a complex condition found +in a real program used by millions of people. The condition is so confusing that even the programmer +who wrote it is not sure if he got it right (see the TODO comment).

    + + + +

    The condition can be simplified by extracting parts of the condition into Boolean-valued methods. +These methods are then used in the condition.

    + + + +
    + + + +
  • +R. C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship, §17.G28. Prentice Hall, 2008. +
  • +
  • +S. McConnell, Code Complete: A Practical Handbook of Software Construction. Microsoft Press, 2004. +
  • + + +
    +
    diff --git a/java/ql/src/Complexity/ComplexCondition.ql b/java/ql/src/Complexity/ComplexCondition.ql new file mode 100644 index 00000000000..3c46be68f76 --- /dev/null +++ b/java/ql/src/Complexity/ComplexCondition.ql @@ -0,0 +1,27 @@ +/** + * @name Complex condition + * @description Very complex conditions are difficult to read and may include defects. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/complex-condition + * @tags testability + * readability + */ +import java + +predicate nontrivialLogicalOperator(BinaryExpr e) { + e instanceof LogicExpr and + not e.getParent().(Expr).getKind() = e.getKind() +} + +Expr getSimpleParent(Expr e) { + result = e.getParent() and + not result instanceof MethodAccess +} + +from Expr e +where + not e.getParent() instanceof Expr and + strictcount(BinaryExpr op | getSimpleParent*(op) = e and nontrivialLogicalOperator(op)) > 5 +select e, "Complex condition: too many logical operations in this expression." diff --git a/java/ql/src/Complexity/ComplexConditionGood.java b/java/ql/src/Complexity/ComplexConditionGood.java new file mode 100644 index 00000000000..2e67d61480b --- /dev/null +++ b/java/ql/src/Complexity/ComplexConditionGood.java @@ -0,0 +1,27 @@ +public class Dialog +{ + // ... + + private void validate() { + if(idIsEmpty() || (noPartnerId() && parameterLengthInvalid())){ // GOOD: Condition is simpler + disableOKButton(); + } else { + enableOKButton(); + } + } + + private boolean idIsEmpty(){ + return id != null && id.length() == 0; + } + + private boolean noPartnerId(){ + return partner == null || partner.id == -1; + } + + private boolean parameterLengthInvalid(){ + return (option == Options.SHORT && parameter.length() == 0) || + (option == Options.LONG && parameter.length() < 8); + } + + // ... +} \ No newline at end of file diff --git a/java/ql/src/DeadCode/DeadClass.java b/java/ql/src/DeadCode/DeadClass.java new file mode 100644 index 00000000000..61270cb2948 --- /dev/null +++ b/java/ql/src/DeadCode/DeadClass.java @@ -0,0 +1,30 @@ +public static void main(String[] args) { + String firstName = /*...*/; String lastName = /*...*/; + // Construct a customer + Customer customer = new Customer(); + // Set important properties (but not the address) + customer.setName(firstName, lastName); + // Save the customer + customer.save(); +} + +public class Customer { + private Address address; + // ... + + // This setter and getter are unused, and so may be deleted. + public void addAddress(String line1, String line2, String line3) { + address = new Address(line1, line2, line3); + } + public Address getAddress() { return address; } +} + +/* + * This class is only constructed from dead code, and may be deleted. + */ +public class Address { + // ... + public Address(String line1, String line2, String line3) { + // ... + } +} diff --git a/java/ql/src/DeadCode/DeadClass.qhelp b/java/ql/src/DeadCode/DeadClass.qhelp new file mode 100644 index 00000000000..518acef8eaf --- /dev/null +++ b/java/ql/src/DeadCode/DeadClass.qhelp @@ -0,0 +1,99 @@ + + + + +

    +Classes that are never used at runtime are redundant and should be removed. +

    + +

    +Classes are considered dead if at runtime: +

    +
      +
    • No methods declared in the class, or a sub-type, are called.
    • +
    • No fields declared in the class, or a sub-type, are read.
    • +
    • The class is never constructed.
    • +
    +

    Any class which is not dead is considered to be "live". Nested classes are considered and listed +separately, as a live nested class within a dead outer class can be moved to a separate file, +allowing the outer class to be deleted. +

    +

    +A special exception is made for "namespace classes". A namespace class is used only to group static +fields, methods and nested classes - it is never instantiated, has no public constructor and has no +instance methods. If a class is considered to be a namespace class, then it is live if at least one +of the static members of that class is live - including static nested classes. +

    + +
    + +

    +Before making any changes, confirm that the class is not required by verifying that the only +dependencies on the class are from other dead classes and methods. This confirmation is necessary +because there may be project-specific frameworks or techniques which can introduce hidden +dependencies. If this project is for a library, then consider whether the class is part of the +external API, and may be used in external projects that are not included in the snapshot. +

    +

    +After confirming that the class is not required, remove the class. You will also need to remove any +references to this class, which may, in turn, require removing other unused classes, methods +and fields (see Example 1). +

    +

    +Nested classes within this type should be moved, either to a new top-level type, or to another +type, unless they are also marked as dead, in which case they can also be removed. Alternatively, +if there are some live nested classes within the dead class, the class can be retained by +converting all live nested classes to static members, and removing all instance methods and fields, +and all dead static members (see Example 2). +

    + +
    +
    +

    +In the following example, we have a number of classes, and an "entry point" in the form of a main +method. +

    + +

    +The class Customer is constructed in the main method, and is therefore live. The +class Address is constructed in setAddress, so we might think that it +would also be live. However, setAddress is never called by the main method, so, +assuming that this is the entire program, an Address is never constructed at runtime. +Therefore, the Address class is dead and can be removed without changing the meaning +of this program. To delete the Address class we will also need to delete the +setAddress and getAddress methods, and the address field, +otherwise the program will not compile. +

    +
    +
    +

    +In the next example, we have a CustomerActions class containing Actions +that affect customers. For example, this could be a Java Swing application, and the +Actions could be actions that are available in the user interface. +

    + +

    +The CustomerActions class has a constructor and an instance method, which are never +called. Instead, actions are instantiated directly. Although this makes the nested +Action classes live, live nested classes do not make the outer class live. Therefore, +the CustomerActions class is marked as dead. +

    +

    +There are two ways to resolve the dead CustomerActions class: +

    +
      +
    • Move each nested static action that is used by the program to a new file, or nest it within +a different class, then delete the dead CustomerActions class.
    • +
    • Convert the CustomerActions class to a namespace class. First convert the +constructor to a suppressed constructor by making it private, preventing the class from +being instantiated, then remove the instance method createAddCustomerAction.
    • +
    +

    +Taking the second approach, this is the final result. +

    + +
    + +
    diff --git a/java/ql/src/DeadCode/DeadClass.ql b/java/ql/src/DeadCode/DeadClass.ql new file mode 100644 index 00000000000..038a7fdfca3 --- /dev/null +++ b/java/ql/src/DeadCode/DeadClass.ql @@ -0,0 +1,35 @@ +/** + * @name Dead class + * @description Dead classes add unnecessary complexity. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/dead-class + * @tags maintainability + * useless-code + * external/cwe/cwe-561 + */ +import semmle.code.java.deadcode.DeadCode + +from DeadClass c, Element origin, string reason +where + if exists(DeadRoot root | root = c.getADeadRoot() | not root = c.getACallable()) then ( + // Report a list of the dead roots. + origin = c.getADeadRoot() and + not origin = c.getACallable() and + // There are uses of this class from outside the class. + reason = " is only used from dead code originating at $@." + ) else ( + // There are no dead roots outside this class. + origin = c and + if c.isUnusedOutsideClass() then + // Never accessed outside this class, so it's entirely unused. + reason = " is entirely unused." + else + /* + * There are no dead roots outside the class, but the class has a possible liveness cause + * external to the class, so it must be accessed from at least one dead-code cycle. + */ + reason = " is only used from or in a dead-code cycle." + ) +select c, "The class " + c.getName() + reason, origin, origin.getName() diff --git a/java/ql/src/DeadCode/DeadCodeDetails.qhelp b/java/ql/src/DeadCode/DeadCodeDetails.qhelp new file mode 100644 index 00000000000..8bbebf28554 --- /dev/null +++ b/java/ql/src/DeadCode/DeadCodeDetails.qhelp @@ -0,0 +1,24 @@ + + + + +

    +A class, method, or field may be dead even if it has dependencies from other parts of the program, +if those dependencies are from code that is also considered to be dead. We can also consider this +from the opposite side - an element is live, if and only if there is an entry point - such as a +main method - that eventually calls the method, reads the field or constructs the class. +

    +

    +When identifying dead code, we make an assumption that the snapshot of the project includes all +possible callers of the code. If the project is a library project, this may not be the case, and +code may be flagged as dead when it is only used by other projects not included in the snapshot. +

    +

    +You can customize the results by defining additional "entry points" or by identifying fields that +are accessed using reflection. You may also wish to "whitelist" classes, methods or fields that +should be excluded from the results. Please refer to the Semmle documentation for more information. +

    +
    +
    diff --git a/java/ql/src/DeadCode/DeadCodeExtraEntryPoints.qhelp b/java/ql/src/DeadCode/DeadCodeExtraEntryPoints.qhelp new file mode 100644 index 00000000000..7342e7fefe2 --- /dev/null +++ b/java/ql/src/DeadCode/DeadCodeExtraEntryPoints.qhelp @@ -0,0 +1,15 @@ + + + + +

    +If you observe a large number of false positives, you may need to add extra entry points to +identify hidden dependencies caused by the use of a particular framework or technique, or to +identify library project entry points. Please refer to the Semmle documentation for more +information on how to do this. +

    +
    + +
    diff --git a/java/ql/src/DeadCode/DeadCodeReferences.qhelp b/java/ql/src/DeadCode/DeadCodeReferences.qhelp new file mode 100644 index 00000000000..9039a5d7772 --- /dev/null +++ b/java/ql/src/DeadCode/DeadCodeReferences.qhelp @@ -0,0 +1,9 @@ + + + +
  • Wikipedia: Redundant code.
  • +
  • CERT Java Coding Standard: MSC56-J. Detect and remove superfluous code and values.
  • +
    +
    \ No newline at end of file diff --git a/java/ql/src/DeadCode/DeadCodeSummary.qhelp b/java/ql/src/DeadCode/DeadCodeSummary.qhelp new file mode 100644 index 00000000000..bb9563b1d59 --- /dev/null +++ b/java/ql/src/DeadCode/DeadCodeSummary.qhelp @@ -0,0 +1,17 @@ + + + + +

    +Redundant, or "dead", code imposes a burden on those reading or maintaining the software project. +It can make it harder to understand the structure of the code, as well as increasing the complexity +of adding new features or fixing bugs. It can also affect compilation and build times for the +project, as dead code will still be compiled and built even if it is never used. In some cases it +may also affect runtime performance - for example, fields that are written to but never read from, +where the value written to the field is expensive to compute. Removing dead code should not +change the meaning of the program. +

    +
    +
    diff --git a/java/ql/src/DeadCode/DeadEnumConstant.java b/java/ql/src/DeadCode/DeadEnumConstant.java new file mode 100644 index 00000000000..1279e8734b2 --- /dev/null +++ b/java/ql/src/DeadCode/DeadEnumConstant.java @@ -0,0 +1,23 @@ +public enum Result { + SUCCESS, + FAILURE, + ERROR +} + +public Result runOperation(String value) { + if (value == 1) { + return SUCCESS; + } else { + return FAILURE; + } +} + +public static void main(String[] args) { + Result operationResult = runOperation(args[0]); + if (operationResult == Result.ERROR) { + exit(1); + } else { + exit(0); + } + +} \ No newline at end of file diff --git a/java/ql/src/DeadCode/DeadEnumConstant.qhelp b/java/ql/src/DeadCode/DeadEnumConstant.qhelp new file mode 100644 index 00000000000..82ba9d36362 --- /dev/null +++ b/java/ql/src/DeadCode/DeadEnumConstant.qhelp @@ -0,0 +1,55 @@ + + + + +

    +Enum constants that are never used at runtime are redundant and should be removed. +

    + +

    +An enum constant is considered dead if at runtime it is never used, or only used in comparisons. Any +enum constant which is not dead is considered to be "live". +

    +

    +An enum constant that is only used in a comparison is considered dead because the comparison will +always produce the same result. This is because no variable holds the value of the enum constant, +so the comparison of any variable against the constant will always return the same result. +

    + +
    + +

    +Before making any changes, confirm that the enum constant is not required by verifying that the +enum constant is never used. This confirmation is necessary because there may be project-specific +frameworks or techniques which can introduce hidden dependencies. If this project is for a library, +then consider whether the enum constant is part of the external API, and may be used in external +projects that are not included in the snapshot. +

    +

    +After confirming that the enum constant is not required, remove the enum constant. You will also +need to remove any references to this enum constant, which may, in turn, require removing other dead +code. +

    + +
    + +

    +In the following example, we have an enum class called Result, intended to report the +result of some operation: +

    + +

    +The method runOperation performs some operation, and returns a Result +depending on whether the operation succeeded. However, it only returns either SUCCESS +or FAILURE, and never ERROR. The main method calls +runOperation, and checks whether the returned result is the ERROR. +However, this check will always return the same result - false. This is because the +operationResult can never hold ERROR, because ERROR is never +stored or returned anywhere in the program. Therefore, ERROR is dead and can be removed, +along with the comparison check, and the exit(1);. +

    +
    + +
    diff --git a/java/ql/src/DeadCode/DeadEnumConstant.ql b/java/ql/src/DeadCode/DeadEnumConstant.ql new file mode 100644 index 00000000000..19044994348 --- /dev/null +++ b/java/ql/src/DeadCode/DeadEnumConstant.ql @@ -0,0 +1,18 @@ +/** + * @name Dead enum constant + * @description Dead enum constants add unnecessary complexity. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/dead-enum-constant + * @tags maintainability + * useless-code + * external/cwe/cwe-561 + */ + +import java +import semmle.code.java.deadcode.DeadCode + +from UnusedEnumConstant e +where not e.whitelisted() +select e, e.getName() + " is unused -- its value is never obtained." diff --git a/java/ql/src/DeadCode/DeadField.java b/java/ql/src/DeadCode/DeadField.java new file mode 100644 index 00000000000..f037f0b4f92 --- /dev/null +++ b/java/ql/src/DeadCode/DeadField.java @@ -0,0 +1,7 @@ +public class FieldOnlyRead { + private int deadField; + + private int getDeadField() { + return deadField; + } +} \ No newline at end of file diff --git a/java/ql/src/DeadCode/DeadField.qhelp b/java/ql/src/DeadCode/DeadField.qhelp new file mode 100644 index 00000000000..f3027a7a9c3 --- /dev/null +++ b/java/ql/src/DeadCode/DeadField.qhelp @@ -0,0 +1,68 @@ + + + + +

    +Fields that are never read at runtime are unnecessary and should be removed. +

    + +

    +Fields are considered dead if at runtime they are never read directly or indirectly, for example +through a framework or a use of reflection. Any field which is not dead is considered to be "live". +

    +

    +Fields are considered to be dead if they are only written to, and never read. +

    + +
    + +

    +Before making any changes, confirm that the field is not required by verifying that the field is +only read from dead methods. This confirmation is necessary because there may be project-specific +frameworks or techniques which can introduce hidden dependencies. If this project is for a library, +then consider whether the field is part of the external API, and may be used in external projects +that are not included in the snapshot. +

    +

    +After confirming that the field is not required, remove the field. You will also need to remove any +references to this field, which may, in turn, require removing other unused classes, methods +and fields. +

    + +
    +
    +

    +In the following example, we have a class containing a single field called deadField: +

    + +

    +The field is only read from the method getDeadField. However, getDeadField +is never called, so the field is never read at runtime. The field is therefore marked as dead. +

    +
    +
    +

    +In this example, we have another class containing a single field called writtenToField: +

    + +

    +The field is written to in the method runThing, which is live because it is called by +the main method. However, the field is never read at runtime, only written to. The +field is therefore marked as dead. +

    +
    +
    +

    +In this example, we have a class representing something that can be serialized to and from XML: +

    + +

    +The field field is written and read by the serialization framework in order to store +the contents of the object in an XML file, or to construct an instance of the object from an XML +file. The field is therefore considered to be read at runtime, which makes the field live. +

    +
    + +
    diff --git a/java/ql/src/DeadCode/DeadField.ql b/java/ql/src/DeadCode/DeadField.ql new file mode 100644 index 00000000000..992e18cd440 --- /dev/null +++ b/java/ql/src/DeadCode/DeadField.ql @@ -0,0 +1,31 @@ +/** + * @name Dead field + * @description Fields that are never read are likely unnecessary. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/dead-field + * @tags maintainability + * useless-code + * external/cwe/cwe-561 + */ + +import java +import semmle.code.java.deadcode.DeadCode + +from DeadField f, Element origin, string reason +where + not f.isInDeadScope() and + if exists(FieldRead read | read = f.getAnAccess()) then ( + if exists(DeadRoot root | root = getADeadRoot(f.getAnAccess().(FieldRead).getEnclosingCallable())) then ( + origin = getADeadRoot(f.getAnAccess().(FieldRead).getEnclosingCallable()) and + reason = " is only read from dead code originating at $@." + ) else ( + origin = f and + reason = " is only read from a dead-code cycle." + ) + ) else ( + origin = f and + reason = " is entirely unread." + ) +select f, "The field " + f.getName() + reason, origin, origin.getName() diff --git a/java/ql/src/DeadCode/DeadFieldSerialized.java b/java/ql/src/DeadCode/DeadFieldSerialized.java new file mode 100644 index 00000000000..0124bd09721 --- /dev/null +++ b/java/ql/src/DeadCode/DeadFieldSerialized.java @@ -0,0 +1,9 @@ +@XmlRootElement +public class SerializableClass { + @XmlAttribute + private String field; + + public void setField(String field) { + this.field = field; + } +} \ No newline at end of file diff --git a/java/ql/src/DeadCode/DeadFieldWrittenTo.java b/java/ql/src/DeadCode/DeadFieldWrittenTo.java new file mode 100644 index 00000000000..46984716a5b --- /dev/null +++ b/java/ql/src/DeadCode/DeadFieldWrittenTo.java @@ -0,0 +1,12 @@ +public class FieldOnlyRead { + private int writtenToField; + + public void runThing() { + writtenToField = 2; + callOtherThing(); + } + + public static main(String[] args) { + runThing(); + } +} \ No newline at end of file diff --git a/java/ql/src/DeadCode/DeadMethod.java b/java/ql/src/DeadCode/DeadMethod.java new file mode 100644 index 00000000000..978488da278 --- /dev/null +++ b/java/ql/src/DeadCode/DeadMethod.java @@ -0,0 +1,23 @@ +public static void main(String[] args) { + // Only call the live method + liveMethod(); +} + +/** This method is live because it is called by main(..) */ +public static void liveMethod() { + otherLiveMethod() +} + +/** This method is live because it is called by a live method */ +public static void otherLiveMethod() { +} + + +/** This method is dead because it is never called */ +public static void deadMethod() { + otherDeadMethod(); +} + +/** This method is dead because it is only called by dead methods */ +public static void otherDeadMethod() { +} \ No newline at end of file diff --git a/java/ql/src/DeadCode/DeadMethod.qhelp b/java/ql/src/DeadCode/DeadMethod.qhelp new file mode 100644 index 00000000000..e3368b54a47 --- /dev/null +++ b/java/ql/src/DeadCode/DeadMethod.qhelp @@ -0,0 +1,81 @@ + + + + +

    +Methods that are never called at runtime are redundant and should be removed. +

    + +

    +Methods are considered dead if at runtime they are never called, either directly, by a method call, +or indirectly, through a framework or use of reflection. Any method which is not dead is considered +to be "live". +

    +

    +The results can include methods, constructors and initializers. Initializers come in two forms, +instance initializers and static initializers. For each class there will be at most one dead +initializer of each type, representing all the initialization of that type in the class. +

    + +
    + +

    +Before making any changes, confirm that the method is not required by verifying that the only +dependencies on the method are from other dead methods. This confirmation is necessary because +there may be project-specific frameworks or techniques which can introduce hidden dependencies. +If this project is for a library, then consider whether the method is part of the external API, +and may be used in external projects that are not included in the snapshot. +

    +

    +After confirming that the method is not required, remove the method. You will also need to remove +any references to this method, which may, in turn, require removing other unused classes, methods +and fields (see Example 1). +

    +

    +If the result is a static initializer, then all static { ... } blocks and +initializers on static fields are dead within that class. In addition, the lack of static +initialization implies that all static methods and fields are also dead and can be removed. These +methods and fields will also be reported separately. In contrast, static nested classes may still +be live, because constructing or accessing the nested static class does not trigger static +initialization of the outer class. +

    +

    +If the result is an instance initializer, then all instance initializer { ... } blocks +and initializers on instance fields are dead. In addition, the lack of instance initialization +implies that the class is never constructed, which means that all instance methods and fields are +also dead and can be removed. These methods and fields will also be reported separately. +

    + +
    +
    +

    +In the following example, we have a number of methods, and an "entry point" in the form of a main +method. +

    + +

    +The method liveMethod is called from the main method, and is therefore considered live. +liveMethod calls otherLiveMethod, which also makes that live. +

    +

    +In contrast, deadMethod is never called, and does not represent an entry point, so is +marked as dead. Likewise, otherDeadMethod is only called from the +deadMethod, so is also marked as dead. +

    +
    +
    +

    +In this example, we have a test class containing a number of methods. +

    + +

    +In this case, no methods are called directly. However, the annotations on the methods indicate that +this is a test class - specifically, JUnit - and that the methods will be called by the test +framework when running the tests. testCustomer and setUp are therefore +considered to be "live". +

    +
    + +
    diff --git a/java/ql/src/DeadCode/DeadMethod.ql b/java/ql/src/DeadCode/DeadMethod.ql new file mode 100644 index 00000000000..ee27a18cd9a --- /dev/null +++ b/java/ql/src/DeadCode/DeadMethod.ql @@ -0,0 +1,33 @@ +/** + * @name Dead method + * @description Dead methods add unnecessary complexity. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/dead-function + * @tags maintainability + * useless-code + * external/cwe/cwe-561 + */ +import java +import semmle.code.java.deadcode.DeadCode + +from DeadMethod c, Callable origin, string reason +where + not c.isInDeadScope() and + if exists(DeadRoot deadRoot | deadRoot = getADeadRoot(c) | deadRoot.getSourceDeclaration() != c) then ( + // We've found a dead root that is not this callable (or an instantiation thereof). + origin = getADeadRoot(c).getSourceDeclaration() and + reason = " is only used from dead code originating at $@." + ) else ( + origin = c and + if exists(Callable cause | cause = possibleLivenessCause(c) and not cause instanceof DeadRoot | + cause.getSourceDeclaration() = c implies possibleLivenessCause(cause).getSourceDeclaration() != c) + then + // There are no dead roots that are not this callable (or an instantiation thereof), and at least one + // liveness cause (ignoring trivial cycles between a parameterized callable and its source declaration). + reason = " is only used from, or in, a dead-code cycle." + else + reason = " is entirely unused." + ) +select c, "The method " + c.getName() + reason, origin, origin.getName() diff --git a/java/ql/src/DeadCode/DeadMethodTest.java b/java/ql/src/DeadCode/DeadMethodTest.java new file mode 100644 index 00000000000..6cbf7d330bc --- /dev/null +++ b/java/ql/src/DeadCode/DeadMethodTest.java @@ -0,0 +1,12 @@ +public class TestClass { + + @Before + public void setUp() { + // ... + } + + @Test + public void testCustomer() { + // ... + } +} \ No newline at end of file diff --git a/java/ql/src/DeadCode/FLinesOfDeadCode.qhelp b/java/ql/src/DeadCode/FLinesOfDeadCode.qhelp new file mode 100644 index 00000000000..215733ed510 --- /dev/null +++ b/java/ql/src/DeadCode/FLinesOfDeadCode.qhelp @@ -0,0 +1,17 @@ + + + + + + + +

    +Any code that is marked as dead should be reviewed and, if it is genuinely not used, deleted. You +can see which classes, methods and fields contribute to this metric using the rules for Dead Code +analysis. +

    +
    + +
    diff --git a/java/ql/src/DeadCode/FLinesOfDeadCode.ql b/java/ql/src/DeadCode/FLinesOfDeadCode.ql new file mode 100644 index 00000000000..4404a6b8d25 --- /dev/null +++ b/java/ql/src/DeadCode/FLinesOfDeadCode.ql @@ -0,0 +1,50 @@ +/** + * @name Lines of dead code in files + * @description The number of lines of dead code in a file. + * @kind treemap + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg sum max + * @id java/lines-of-dead-code + * @tags maintainability + * external/cwe/cwe-561 + */ +import java +import semmle.code.java.deadcode.DeadCode + +from File f, int n +where + n = + // Lines of code contributed by dead classes. + sum(DeadClass deadClass | deadClass.getFile() = f | + deadClass.getNumberOfLinesOfCode() - + /* + * Remove inner and local classes, as they are reported as separate dead classes. Do not + * remove anonymous classes, because they aren't reported separately. + */ + sum(NestedClass innerClass | innerClass.getEnclosingType() = deadClass and not innerClass.isAnonymous() | + innerClass.getNumberOfLinesOfCode() + ) + ) + + // Lines of code contributed by dead methods, not in dead classes. + sum(DeadMethod deadMethod | deadMethod.getFile() = f and not deadMethod.isInDeadScope() | + deadMethod.getNumberOfLinesOfCode() - + /* + * Remove local classes defined in the dead method - they are reported separately as a dead + * class. We keep anonymous class counts, because anonymous classes are not reported + * separately. + */ + sum(LocalClass localClass | localClass.getLocalClassDeclStmt().getEnclosingCallable() = deadMethod | + localClass.getNumberOfLinesOfCode() + ) + ) + + // Lines of code contributed by dead fields, not in dead classes. + sum(DeadField deadField | deadField.getFile() = f and not deadField.isInDeadScope() | + deadField.getNumberOfLinesOfCode() + ) + + // Lines of code contributed by unused enum constants. + sum(UnusedEnumConstant deadEnumConstant | deadEnumConstant.getFile() = f | + deadEnumConstant.getNumberOfLinesOfCode() + ) +select f, n +order by n desc diff --git a/java/ql/src/DeadCode/NamespaceClass.java b/java/ql/src/DeadCode/NamespaceClass.java new file mode 100644 index 00000000000..2fe545a9d4a --- /dev/null +++ b/java/ql/src/DeadCode/NamespaceClass.java @@ -0,0 +1,25 @@ +/* + * This class is dead because it is never constructed, and the instance methods are not + * called. + */ +public class CustomerActions { + public CustomerActions() { + } + + // This method is never called, + public Action createAddCustomerAction () { + return new AddCustomerAction(); + } + + // These two are used directly + public static class AddCustomerAction extends Action { /* ... */ } + public static class RemoveCustomerAction extends Action { /* ... */ } +} + +public static void main(String[] args) { + // Construct the actions directly + Action action = new CustomerActions.AddCustomerAction(); + action.run(); + Action action = new CustomerActions.RemoveCustomerAction(); + action.run(); +} diff --git a/java/ql/src/DeadCode/NamespaceClass2.java b/java/ql/src/DeadCode/NamespaceClass2.java new file mode 100644 index 00000000000..7cd2f7363d7 --- /dev/null +++ b/java/ql/src/DeadCode/NamespaceClass2.java @@ -0,0 +1,19 @@ +// This class is now live - it is used as a namespace class +public class CustomerActions { + /* + * This constructor is suppressing construction of this class, so is not considered + * dead. + */ + private CustomerActions() { } + // These two are used directly + public static class AddCustomerAction extends Action { /* ... */ } + public static class RemoveCustomerAction extends Action { /* ... */ } +} + +public static void main(String[] args) { + // Construct the actions directly + Action action = new CustomerActions.AddCustomerAction(); + action.run(); + Action action = new CustomerActions.RemoveCustomerAction(); + action.run(); +} diff --git a/java/ql/src/DeadCode/UselessParameter.java b/java/ql/src/DeadCode/UselessParameter.java new file mode 100644 index 00000000000..56a0dc22b31 --- /dev/null +++ b/java/ql/src/DeadCode/UselessParameter.java @@ -0,0 +1,3 @@ +public void isAbsolutePath(String path, String name) { + return path.startsWith("/") || path.startsWith("\\"); +} \ No newline at end of file diff --git a/java/ql/src/DeadCode/UselessParameter.qhelp b/java/ql/src/DeadCode/UselessParameter.qhelp new file mode 100644 index 00000000000..fd05dd62371 --- /dev/null +++ b/java/ql/src/DeadCode/UselessParameter.qhelp @@ -0,0 +1,39 @@ + + + +

    +Parameters that are never read in the body of the method, and are not required due to overriding, +are useless and can be removed. Useless parameters unnecessarily complicate the interface for that +method, and cause a maintenance and development burden. +

    +

    +Methods with useless parameters indicate that either the method can be simplified by removing the +parameter, or that the method is not using a value it should be using. Parameters of methods that +override other methods will not be marked as useless, because they are required. Similarly, +parameters of methods that are overridden by other methods are not marked as useless if they are +used by one of the overriding methods. +

    +
    + +

    +The method should be inspected to determine whether the parameter should be used within the body. +If the method is overridden, also consider whether any override methods should be using the +parameter. If the parameter is not required, it should be removed. +

    +
    + +

    +In the following example, we have a method for determining whether a String path +is an absolute path: +

    + +

    +The method uses the parameter path to determine the return value. However, the +parameter name is not used within the body of the method. The parameter will be marked +as useless, and can be removed from the program. +

    +
    + +
    diff --git a/java/ql/src/DeadCode/UselessParameter.ql b/java/ql/src/DeadCode/UselessParameter.ql new file mode 100644 index 00000000000..b7b4e66c901 --- /dev/null +++ b/java/ql/src/DeadCode/UselessParameter.ql @@ -0,0 +1,16 @@ +/** + * @name Useless parameter + * @description Parameters that are not used add unnecessary complexity to an interface. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/unused-parameter + * @tags maintainability + * useless-code + * external/cwe/cwe-561 + */ +import semmle.code.java.deadcode.DeadCode + +from RootdefCallable c +where not c.whitelisted() +select c.unusedParameter() as p, "The parameter " + p + " is unused." diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbContainerInterference.qhelp b/java/ql/src/Frameworks/JavaEE/EJB/EjbContainerInterference.qhelp new file mode 100644 index 00000000000..739ad1567fb --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbContainerInterference.qhelp @@ -0,0 +1,36 @@ + + + + + +

    +The Enterprise JavaBeans 3.0 core specification, Section 21.1.2, states: +

    + +
    +

    +The enterprise bean must not attempt to create a class loader; obtain the current class loader; +set the context class loader; set security manager; create a new security manager; stop the +JVM; or change the input, output, and error streams. +

    +

    +These functions are reserved for the EJB container. Allowing the enterprise bean to use these functions +could compromise security and decrease the container's ability to properly manage the runtime environment. +

    +
    + +
    + + + +
  • + + JSR-220 Enterprise JavaBeans 3.0 Final Release (ejbcore), + Section 21.1.2 Programming Restrictions +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbContainerInterference.ql b/java/ql/src/Frameworks/JavaEE/EJB/EjbContainerInterference.ql new file mode 100644 index 00000000000..71f145f34e4 --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbContainerInterference.ql @@ -0,0 +1,37 @@ +/** + * @name EJB interferes with container operation + * @description An EJB should not attempt to create a class loader, + * obtain the current class loader, set the context class loader, + * set a security manager, create a new security manager, + * stop the JVM, or change the input, output or error streams. + * Such operations could interfere with the EJB container's operation. + * @kind problem + * @problem.severity error + * @precision low + * @id java/ejb/container-interference + * @tags reliability + * external/cwe/cwe-578 + * external/cwe/cwe-382 + */ +import java +import semmle.code.java.frameworks.javaee.ejb.EJB +import semmle.code.java.frameworks.javaee.ejb.EJBRestrictions + +/* +JSR 220: Enterprise JavaBeansTM,Version 3.0 +EJB Core Contracts and Requirements +Section 21.1.2 Programming Restrictions + +- The enterprise bean must not attempt to create a class loader; obtain the current class loader; +set the context class loader; set security manager; create a new security manager; stop the +JVM; or change the input, output, and error streams. + +These functions are reserved for the EJB container. Allowing the enterprise bean to use these functions +could compromise security and decrease the container's ability to properly manage the runtime envi- +ronment. +*/ + +from Callable origin, ForbiddenContainerInterferenceCallable target, Call call +where ejbCalls(origin, target, call) +select origin, "EJB should not interfere with its container's operation by calling $@.", + call, target.getDeclaringType().getName() + "." + target.getName() diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbFileIO.qhelp b/java/ql/src/Frameworks/JavaEE/EJB/EjbFileIO.qhelp new file mode 100644 index 00000000000..608ee21f854 --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbFileIO.qhelp @@ -0,0 +1,35 @@ + + + + + +

    +The Enterprise JavaBeans 3.0 core specification, Section 21.1.2, states: +

    + +
    +

    +An enterprise bean must not use the java.io package to attempt to access files and +directories in the file system. +

    +

    +The file system APIs are not well-suited for business components to access data. Business components +should use a resource manager API, such as JDBC, to store data. +

    +
    + +
    + + + +
  • + + JSR-220 Enterprise JavaBeans 3.0 Final Release (ejbcore), + Section 21.1.2 Programming Restrictions +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbFileIO.ql b/java/ql/src/Frameworks/JavaEE/EJB/EjbFileIO.ql new file mode 100644 index 00000000000..7b39f3b5c65 --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbFileIO.ql @@ -0,0 +1,36 @@ +/** + * @name EJB uses file input/output + * @description An EJB should not attempt to access files or directories in the file system. + * Such use could compromise security and is not a suitable data access method + * for enterprise components. + * @kind problem + * @problem.severity error + * @precision low + * @id java/ejb/file-io + * @tags reliability + * external/cwe/cwe-576 + */ +import java +import semmle.code.java.frameworks.javaee.ejb.EJB +import semmle.code.java.frameworks.javaee.ejb.EJBRestrictions + +/* +JSR 220: Enterprise JavaBeansTM,Version 3.0 +EJB Core Contracts and Requirements +Section 21.1.2 Programming Restrictions + +- An enterprise bean must not use the java.io package to attempt to access files and directo- +ries in the file system. + +The file system APIs are not well-suited for business components to access data. Business components +should use a resource manager API, such as JDBC, to store data. + +- The enterprise bean must not attempt to directly read or write a file descriptor. + +Allowing the enterprise bean to read and write file descriptors directly could compromise security. +*/ + +from Callable origin, ForbiddenFileCallable target, Call call +where ejbCalls(origin, target, call) +select origin, "EJB should not access the file system by calling $@.", + call, target.getDeclaringType().getName() + "." + target.getName() diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbGraphics.qhelp b/java/ql/src/Frameworks/JavaEE/EJB/EjbGraphics.qhelp new file mode 100644 index 00000000000..d32bc3078b1 --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbGraphics.qhelp @@ -0,0 +1,35 @@ + + + + + +

    +The Enterprise JavaBeans 3.0 core specification, Section 21.1.2, states: +

    + +
    +

    +An enterprise bean must not use the AWT functionality to attempt to output information to a +display, or to input information from a keyboard. +

    +

    +Most servers do not allow direct interaction between an application program and a keyboard/display +attached to the server system. +

    +
    + +
    + + + +
  • + + JSR-220 Enterprise JavaBeans 3.0 Final Release (ejbcore), + Section 21.1.2 Programming Restrictions +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbGraphics.ql b/java/ql/src/Frameworks/JavaEE/EJB/EjbGraphics.ql new file mode 100644 index 00000000000..cdda4120b48 --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbGraphics.ql @@ -0,0 +1,32 @@ +/** + * @name EJB uses graphics + * @description An EJB should not use AWT or other graphics functionality. + * Such operations are normally performed by an end-user interface + * that accesses a server but not by the server itself. + * @kind problem + * @problem.severity error + * @precision low + * @id java/ejb/graphics + * @tags reliability + * external/cwe/cwe-575 + */ +import java +import semmle.code.java.frameworks.javaee.ejb.EJB +import semmle.code.java.frameworks.javaee.ejb.EJBRestrictions + +/* +JSR 220: Enterprise JavaBeansTM,Version 3.0 +EJB Core Contracts and Requirements +Section 21.1.2 Programming Restrictions + +- An enterprise bean must not use the AWT functionality to attempt to output information to a +display, or to input information from a keyboard. + +Most servers do not allow direct interaction between an application program and a keyboard/display +attached to the server system. +*/ + +from Callable origin, ForbiddenGraphicsCallable target, Call call +where ejbCalls(origin, target, call) +select origin, "EJB should not use AWT or other graphics functionality by $@.", + call, target.getDeclaringType().getName() + "." + target.getName() diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbNative.qhelp b/java/ql/src/Frameworks/JavaEE/EJB/EjbNative.qhelp new file mode 100644 index 00000000000..e854c0d7a79 --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbNative.qhelp @@ -0,0 +1,34 @@ + + + + + +

    +The Enterprise JavaBeans 3.0 core specification, Section 21.1.2, states: +

    + +
    +

    +The enterprise bean must not attempt to load a native library. +

    +

    +This function is reserved for the EJB container. Allowing the enterprise bean to load native code would +create a security hole. +

    +
    + +
    + + + +
  • + + JSR-220 Enterprise JavaBeans 3.0 Final Release (ejbcore), + Section 21.1.2 Programming Restrictions +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbNative.ql b/java/ql/src/Frameworks/JavaEE/EJB/EjbNative.ql new file mode 100644 index 00000000000..d8272e1d5d9 --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbNative.ql @@ -0,0 +1,30 @@ +/** + * @name EJB uses native code + * @description An EJB should not attempt to load or execute native code. + * Such use could compromise security and system stability. + * @kind problem + * @problem.severity error + * @precision low + * @id java/ejb/native-code + * @tags reliability + * external/cwe/cwe-573 + */ +import java +import semmle.code.java.frameworks.javaee.ejb.EJB +import semmle.code.java.frameworks.javaee.ejb.EJBRestrictions + +/* +JSR 220: Enterprise JavaBeansTM,Version 3.0 +EJB Core Contracts and Requirements +Section 21.1.2 Programming Restrictions + +- The enterprise bean must not attempt to load a native library. + +This function is reserved for the EJB container. Allowing the enterprise bean to load native code would +create a security hole. +*/ + +from Callable origin, ForbiddenNativeCallable target, Call call +where ejbCalls(origin, target, call) +select origin, "EJB should not use native code by calling $@.", + call, target.getDeclaringType().getName() + "." + target.getName() diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbReflection.qhelp b/java/ql/src/Frameworks/JavaEE/EJB/EjbReflection.qhelp new file mode 100644 index 00000000000..5a9dd068559 --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbReflection.qhelp @@ -0,0 +1,37 @@ + + + + + +

    +The Enterprise JavaBeans 3.0 core specification, Section 21.1.2, states: +

    + +
    +

    +The enterprise bean must not attempt to query a class to obtain information about the declared +members that are not otherwise accessible to the enterprise bean because of the security rules +of the Java language. The enterprise bean must not attempt to use the Reflection API to access +information that the security rules of the Java programming language make unavailable. +

    +

    +Allowing the enterprise bean to access information about other classes and to access the classes in a +manner that is normally disallowed by the Java programming language could compromise security. +

    +
    + +
    + + + +
  • + + JSR-220 Enterprise JavaBeans 3.0 Final Release (ejbcore), + Section 21.1.2 Programming Restrictions +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbReflection.ql b/java/ql/src/Frameworks/JavaEE/EJB/EjbReflection.ql new file mode 100644 index 00000000000..b22543b9acd --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbReflection.ql @@ -0,0 +1,32 @@ +/** + * @name EJB uses reflection + * @description An EJB should not attempt to use the Reflection API, + * as this could compromise security. + * @kind problem + * @problem.severity error + * @precision low + * @id java/ejb/reflection + * @tags external/cwe/cwe-573 + */ +import java +import semmle.code.java.frameworks.javaee.ejb.EJB +import semmle.code.java.frameworks.javaee.ejb.EJBRestrictions + +/* +JSR 220: Enterprise JavaBeansTM,Version 3.0 +EJB Core Contracts and Requirements +Section 21.1.2 Programming Restrictions + +- The enterprise bean must not attempt to query a class to obtain information about the declared +members that are not otherwise accessible to the enterprise bean because of the security rules +of the Java language. The enterprise bean must not attempt to use the Reflection API to access +information that the security rules of the Java programming language make unavailable. + +Allowing the enterprise bean to access information about other classes and to access the classes in a +manner that is normally disallowed by the Java programming language could compromise security. +*/ + +from Callable origin, ForbiddenReflectionCallable target, Call call +where ejbCalls(origin, target, call) +select origin, "EJB should not use reflection by calling $@.", + call, target.getDeclaringType().getName() + "." + target.getName() diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbSecurityConfiguration.qhelp b/java/ql/src/Frameworks/JavaEE/EJB/EjbSecurityConfiguration.qhelp new file mode 100644 index 00000000000..f9b41c4b6f5 --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbSecurityConfiguration.qhelp @@ -0,0 +1,35 @@ + + + + + +

    +The Enterprise JavaBeans 3.0 core specification, Section 21.1.2, states: +

    + +
    +

    +The enterprise bean must not attempt to access or modify the security configuration objects +(Policy, Security, Provider, Signer, and Identity). +

    +

    +These functions are reserved for the EJB container. Allowing the enterprise bean to use these functions +could compromise security. +

    +
    + +
    + + + +
  • + + JSR-220 Enterprise JavaBeans 3.0 Final Release (ejbcore), + Section 21.1.2 Programming Restrictions +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbSecurityConfiguration.ql b/java/ql/src/Frameworks/JavaEE/EJB/EjbSecurityConfiguration.ql new file mode 100644 index 00000000000..17f549d3dce --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbSecurityConfiguration.ql @@ -0,0 +1,36 @@ +/** + * @name EJB accesses security configuration + * @description An EJB should not attempt to access or modify any Java security configuration, + * including the Policy, Security, Provider, Signer and Identity objects. + * This functionality is reserved for the EJB container for security reasons. + * @kind problem + * @problem.severity error + * @precision low + * @id java/ejb/security-configuration-access + * @tags external/cwe/cwe-573 + */ +import java +import semmle.code.java.frameworks.javaee.ejb.EJB +import semmle.code.java.frameworks.javaee.ejb.EJBRestrictions + +/* +JSR 220: Enterprise JavaBeansTM,Version 3.0 +EJB Core Contracts and Requirements +Section 21.1.2 Programming Restrictions + +- The enterprise bean must not attempt to obtain the security policy information for a particular +code source. + +Allowing the enterprise bean to access the security policy information would create a security hole. + +- The enterprise bean must not attempt to access or modify the security configuration objects +(Policy, Security, Provider, Signer, and Identity). + +These functions are reserved for the EJB container. Allowing the enterprise bean to use these functions +could compromise security. +*/ + +from Callable origin, ForbiddenSecurityConfigurationCallable target, Call call +where ejbCalls(origin, target, call) +select origin, "EJB should not access a security configuration by calling $@.", + call, target.getDeclaringType().getName() + "." + target.getName() diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbSerialization.qhelp b/java/ql/src/Frameworks/JavaEE/EJB/EjbSerialization.qhelp new file mode 100644 index 00000000000..d9e9f920d26 --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbSerialization.qhelp @@ -0,0 +1,34 @@ + + + + + +

    +The Enterprise JavaBeans 3.0 core specification, Section 21.1.2, states: +

    + +
    +

    +The enterprise bean must not attempt to use the subclass and object substitution features of the +Java Serialization Protocol. +

    +

    +Allowing the enterprise bean to use these functions could compromise security. +

    +
    + +
    + + + +
  • + + JSR-220 Enterprise JavaBeans 3.0 Final Release (ejbcore), + Section 21.1.2 Programming Restrictions +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbSerialization.ql b/java/ql/src/Frameworks/JavaEE/EJB/EjbSerialization.ql new file mode 100644 index 00000000000..08954d27a04 --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbSerialization.ql @@ -0,0 +1,29 @@ +/** + * @name EJB uses substitution in serialization + * @description An EJB should not use the subclass or object substitution features of + * the Java serialization protocol, since their use could compromise security. + * @kind problem + * @problem.severity error + * @precision low + * @id java/ejb/substitution-in-serialization + * @tags external/cwe/cwe-573 + */ +import java +import semmle.code.java.frameworks.javaee.ejb.EJB +import semmle.code.java.frameworks.javaee.ejb.EJBRestrictions + +/* +JSR 220: Enterprise JavaBeansTM,Version 3.0 +EJB Core Contracts and Requirements +Section 21.1.2 Programming Restrictions + +- The enterprise bean must not attempt to use the subclass and object substitution features of the +Java Serialization Protocol. + +Allowing the enterprise bean to use these functions could compromise security. +*/ + +from Callable origin, ForbiddenSerializationCallable target, Call call +where ejbCalls(origin, target, call) +select origin, "EJB should not use a substitution feature of serialization by calling $@.", + call, target.getDeclaringType().getName() + "." + target.getName() + " $@" diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbSetSocketOrUrlFactory.qhelp b/java/ql/src/Frameworks/JavaEE/EJB/EjbSetSocketOrUrlFactory.qhelp new file mode 100644 index 00000000000..482fba8af8d --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbSetSocketOrUrlFactory.qhelp @@ -0,0 +1,36 @@ + + + + + +

    +The Enterprise JavaBeans 3.0 core specification, Section 21.1.2, states: +

    + +
    +

    +The enterprise bean must not attempt to set the socket factory used by ServerSocket, Socket, or +the stream handler factory used by URL. +

    +

    +These networking functions are reserved for the EJB container. Allowing the enterprise bean to use +these functions could compromise security and decrease the container's ability to properly manage the +runtime environment. +

    +
    + +
    + + + +
  • + + JSR-220 Enterprise JavaBeans 3.0 Final Release (ejbcore), + Section 21.1.2 Programming Restrictions +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbSetSocketOrUrlFactory.ql b/java/ql/src/Frameworks/JavaEE/EJB/EjbSetSocketOrUrlFactory.ql new file mode 100644 index 00000000000..f27f4e18c3a --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbSetSocketOrUrlFactory.ql @@ -0,0 +1,33 @@ +/** + * @name EJB sets socket factory or URL stream handler factory + * @description An EJB should not set the socket factory used by ServerSocket or Socket, + * or the stream handler factory used by URL. Such operations could + * compromise security or interfere with the EJB container's operation. + * @kind problem + * @problem.severity error + * @precision low + * @id java/ejb/socket-or-stream-handler-factory + * @tags reliability + * external/cwe/cwe-577 + */ +import java +import semmle.code.java.frameworks.javaee.ejb.EJB +import semmle.code.java.frameworks.javaee.ejb.EJBRestrictions + +/* +JSR 220: Enterprise JavaBeansTM,Version 3.0 +EJB Core Contracts and Requirements +Section 21.1.2 Programming Restrictions + +- The enterprise bean must not attempt to set the socket factory used by ServerSocket, Socket, or +the stream handler factory used by URL. + +These networking functions are reserved for the EJB container. Allowing the enterprise bean to use +these functions could compromise security and decrease the container's ability to properly manage the +runtime environment. +*/ + +from Callable origin, ForbiddenSetFactoryCallable target, Call call +where ejbCalls(origin, target, call) +select origin, "EJB should not set a factory by calling $@.", + call, target.getDeclaringType().getName() + "." + target.getName() diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbSocketAsServer.qhelp b/java/ql/src/Frameworks/JavaEE/EJB/EjbSocketAsServer.qhelp new file mode 100644 index 00000000000..12c5f101a67 --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbSocketAsServer.qhelp @@ -0,0 +1,36 @@ + + + + + +

    +The Enterprise JavaBeans 3.0 core specification, Section 21.1.2, states: +

    + +
    +

    +An enterprise bean must not attempt to listen on a socket, accept connections on a socket, or +use a socket for multicast. +

    +

    +The EJB architecture allows an enterprise bean instance to be a network socket client, but it does not +allow it to be a network server. Allowing the instance to become a network server would conflict with +the basic function of the enterprise bean—to serve the EJB clients. +

    +
    + +
    + + + +
  • + + JSR-220 Enterprise JavaBeans 3.0 Final Release (ejbcore), + Section 21.1.2 Programming Restrictions +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbSocketAsServer.ql b/java/ql/src/Frameworks/JavaEE/EJB/EjbSocketAsServer.ql new file mode 100644 index 00000000000..a1c5bff5d55 --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbSocketAsServer.ql @@ -0,0 +1,33 @@ +/** + * @name EJB uses server socket + * @description An EJB should not attempt to listen to or accept connections on a socket, + * or use a socket for multicast. Functioning as a general network server + * would conflict with the EJB's purpose to serve EJB clients. + * @kind problem + * @problem.severity error + * @precision low + * @id java/ejb/server-socket + * @tags reliability + * external/cwe/cwe-577 + */ +import java +import semmle.code.java.frameworks.javaee.ejb.EJB +import semmle.code.java.frameworks.javaee.ejb.EJBRestrictions + +/* +JSR 220: Enterprise JavaBeansTM,Version 3.0 +EJB Core Contracts and Requirements +Section 21.1.2 Programming Restrictions + +- An enterprise bean must not attempt to listen on a socket, accept connections on a socket, or +use a socket for multicast. + +The EJB architecture allows an enterprise bean instance to be a network socket client, but it does not +allow it to be a network server. Allowing the instance to become a network server would conflict with +the basic function of the enterprise bean -- to serve the EJB clients. +*/ + +from Callable origin, ForbiddenServerSocketCallable target, Call call +where ejbCalls(origin, target, call) +select origin, "EJB should not use a socket as a server by calling $@.", + call, target.getDeclaringType().getName() + "." + target.getName() diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbStaticFieldNonFinal.qhelp b/java/ql/src/Frameworks/JavaEE/EJB/EjbStaticFieldNonFinal.qhelp new file mode 100644 index 00000000000..b23b33a2fd4 --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbStaticFieldNonFinal.qhelp @@ -0,0 +1,37 @@ + + + + + +

    +The Enterprise JavaBeans 3.0 core specification, Section 21.1.2, states: +

    + +
    +

    +An enterprise bean must not use read/write static fields. Using read-only static fields is +allowed. Therefore, it is recommended that all static fields in the enterprise bean class be +declared as final. +

    +

    +This rule is required to ensure consistent runtime semantics because while some EJB containers may +use a single JVM to execute all enterprise bean's instances, others may distribute the instances across +multiple JVMs. +

    +
    + +
    + + + +
  • + + JSR-220 Enterprise JavaBeans 3.0 Final Release (ejbcore), + Section 21.1.2 Programming Restrictions +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbStaticFieldNonFinal.ql b/java/ql/src/Frameworks/JavaEE/EJB/EjbStaticFieldNonFinal.ql new file mode 100644 index 00000000000..3bc0bc8d4fa --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbStaticFieldNonFinal.ql @@ -0,0 +1,38 @@ +/** + * @name EJB uses non-final static field + * @description An EJB should not make use of non-final static fields, + * since a consistent state of such fields is not guaranteed + * if an EJB instance is distributed across multiple JVMs. + * @kind problem + * @problem.severity error + * @precision low + * @id java/ejb/non-final-static-field + * @tags reliability + * external/cwe/cwe-573 + */ +import java +import semmle.code.java.frameworks.javaee.ejb.EJB +import semmle.code.java.frameworks.javaee.ejb.EJBRestrictions + +/* +JSR 220: Enterprise JavaBeansTM,Version 3.0 +EJB Core Contracts and Requirements +Section 21.1.2 Programming Restrictions + +- An enterprise bean must not use read/write static fields. Using read-only static fields is +allowed. Therefore, it is recommended that all static fields in the enterprise bean class be +declared as final. + +This rule is required to ensure consistent runtime semantics because while some EJB containers may +use a single JVM to execute all enterprise bean's instances, others may distribute the instances across +multiple JVMs. +*/ + +from Callable origin, ForbiddenStaticFieldCallable target, Call call, FieldAccess fa, Field f +where + ejbCalls(origin, target, call) and + fa = forbiddenStaticFieldUse(target) and + fa.getField() = f +select origin, "EJB should not access non-final static field $@ $@.", + f, f.getDeclaringType().getName() + "." + f.getName(), + fa, "here" diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbSynchronization.qhelp b/java/ql/src/Frameworks/JavaEE/EJB/EjbSynchronization.qhelp new file mode 100644 index 00000000000..affe6c64c35 --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbSynchronization.qhelp @@ -0,0 +1,35 @@ + + + + + +

    +The Enterprise JavaBeans 3.0 core specification, Section 21.1.2, states: +

    + +
    +

    +An enterprise bean must not use thread synchronization primitives to synchronize execution of +multiple instances. +

    +

    +Synchronization would not work if the EJB container distributed +enterprise bean's instances across multiple JVMs. +

    +
    + +
    + + + +
  • + + JSR-220 Enterprise JavaBeans 3.0 Final Release (ejbcore), + Section 21.1.2 Programming Restrictions +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbSynchronization.ql b/java/ql/src/Frameworks/JavaEE/EJB/EjbSynchronization.ql new file mode 100644 index 00000000000..8f2207a614e --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbSynchronization.ql @@ -0,0 +1,31 @@ +/** + * @name EJB uses synchronization + * @description An EJB should not use synchronization, since it will not work properly + * if an EJB is distributed across multiple JVMs. + * @kind problem + * @problem.severity error + * @precision low + * @id java/ejb/synchronization + * @tags reliability + * external/cwe/cwe-574 + */ +import java +import semmle.code.java.frameworks.javaee.ejb.EJB +import semmle.code.java.frameworks.javaee.ejb.EJBRestrictions + +/* +JSR 220: Enterprise JavaBeansTM,Version 3.0 +EJB Core Contracts and Requirements +Section 21.1.2 Programming Restrictions + +- An enterprise bean must not use thread synchronization primitives to synchronize execution of +multiple instances. + +This is for the same reason as above. Synchronization would not work if the EJB container distributed +enterprise bean's instances across multiple JVMs. +*/ + +from Callable origin, ForbiddenSynchronizationCallable target, Call call +where ejbCalls(origin, target, call) +select origin, "EJB should not use synchronization by calling $@.", + call, target.getDeclaringType().getName() + "." + target.getName() diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbThis.qhelp b/java/ql/src/Frameworks/JavaEE/EJB/EjbThis.qhelp new file mode 100644 index 00000000000..681a2d48a5b --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbThis.qhelp @@ -0,0 +1,33 @@ + + + + + +

    +The Enterprise JavaBeans 3.0 core specification, Section 21.1.2, states: +

    + +
    +

    +The enterprise bean must not attempt to pass this as an argument or method result. The +enterprise bean must pass the result of SessionContext.getBusinessObject, +SessionContext.getEJBObject, SessionContext.getEJBLocalObject, +EntityContext.getEJBObject, or EntityContext.getEJBLocalObject instead. +

    +
    + +
    + + + +
  • + + JSR-220 Enterprise JavaBeans 3.0 Final Release (ejbcore), + Section 21.1.2 Programming Restrictions +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbThis.ql b/java/ql/src/Frameworks/JavaEE/EJB/EjbThis.ql new file mode 100644 index 00000000000..1a5f73ea4df --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbThis.ql @@ -0,0 +1,34 @@ +/** + * @name EJB uses 'this' as argument or result + * @description An EJB should not use 'this' as a method argument or result. + * Instead, it should use the result of SessionContext.getBusinessObject, + * SessionContext.getEJBObject, SessionContext.getEJBLocalObject, + * EntityContext.getEJBObject, or EntityContext.getEJBLocalObject. + * @kind problem + * @problem.severity error + * @precision low + * @id java/ejb/this + * @tags portability + * external/cwe/cwe-573 + */ +import java +import semmle.code.java.frameworks.javaee.ejb.EJB +import semmle.code.java.frameworks.javaee.ejb.EJBRestrictions + +/* +JSR 220: Enterprise JavaBeansTM,Version 3.0 +EJB Core Contracts and Requirements +Section 21.1.2 Programming Restrictions + +- The enterprise bean must not attempt to pass this as an argument or method result. The +enterprise bean must pass the result of SessionContext.getBusinessObject, +SessionContext.getEJBObject, SessionContext.getEJBLocalObject, +EntityContext.getEJBObject, or EntityContext.getEJBLocalObject instead. +*/ + +from Callable origin, ForbiddenThisCallable target, Call call, ThisAccess ta +where + ejbCalls(origin, target, call) and + ta = forbiddenThisUse(target) +select origin, "EJB should not use 'this' as a method argument or result $@.", + ta, "here" diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbThreads.qhelp b/java/ql/src/Frameworks/JavaEE/EJB/EjbThreads.qhelp new file mode 100644 index 00000000000..61b3fc783dc --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbThreads.qhelp @@ -0,0 +1,36 @@ + + + + + +

    +The Enterprise JavaBeans 3.0 core specification, Section 21.1.2, states: +

    + +
    +

    +The enterprise bean must not attempt to manage threads. The enterprise bean must not attempt +to start, stop, suspend, or resume a thread, or to change a thread's priority or name. The +enterprise bean must not attempt to manage thread groups. +

    +

    +These functions are reserved for the EJB container. Allowing the enterprise bean to manage threads +would decrease the container's ability to properly manage the runtime environment. +

    +
    + +
    + + + +
  • + + JSR-220 Enterprise JavaBeans 3.0 Final Release (ejbcore), + Section 21.1.2 Programming Restrictions +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/JavaEE/EJB/EjbThreads.ql b/java/ql/src/Frameworks/JavaEE/EJB/EjbThreads.ql new file mode 100644 index 00000000000..0462c6210f4 --- /dev/null +++ b/java/ql/src/Frameworks/JavaEE/EJB/EjbThreads.ql @@ -0,0 +1,33 @@ +/** + * @name EJB uses threads + * @description An EJB should not attempt to manage threads, + * as it could interfere with the EJB container's operation. + * @kind problem + * @problem.severity error + * @precision low + * @id java/ejb/threads + * @tags reliability + * external/cwe/cwe-383 + * external/cwe/cwe-573 + */ +import java +import semmle.code.java.frameworks.javaee.ejb.EJB +import semmle.code.java.frameworks.javaee.ejb.EJBRestrictions + +/* +JSR 220: Enterprise JavaBeansTM,Version 3.0 +EJB Core Contracts and Requirements +Section 21.1.2 Programming Restrictions + +- The enterprise bean must not attempt to manage threads. The enterprise bean must not attempt +to start, stop, suspend, or resume a thread, or to change a thread's priority or name. The enter- +prise bean must not attempt to manage thread groups. + +These functions are reserved for the EJB container. Allowing the enterprise bean to manage threads +would decrease the container's ability to properly manage the runtime environment. +*/ + +from Callable origin, ForbiddenThreadingCallable target, Call call +where ejbCalls(origin, target, call) +select origin, "EJB should not attempt to manage threads by calling $@.", + call, target.getDeclaringType().getName() + "." + target.getName() diff --git a/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/MissingParentBean.qhelp b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/MissingParentBean.qhelp new file mode 100644 index 00000000000..21e647b38a0 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/MissingParentBean.qhelp @@ -0,0 +1,44 @@ + + + + + +

    +Beans that share a considerable number of similar properties exhibit unnecessary repetition in the +bean definitions and make the system's architecture more difficult to see. +

    + +
    + +

    +Try to move the properties that the bean definitions share to a common parent bean. This reduces repetition +in the bean definitions and gives a clearer picture of the system's architecture. +

    + +
    + + +

    The following example shows a configuration file that contains two beans that share several +properties with the same values.

    + + + +

    The following example shows how the shared properties have been moved into a parent bean, +baseService.

    + + + +
    + + + +
  • +Spring Framework Reference Documentation 3.0: +3.4.2.2 References to other beans (collaborators). +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/MissingParentBean.ql b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/MissingParentBean.ql new file mode 100644 index 00000000000..2cbd7644488 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/MissingParentBean.ql @@ -0,0 +1,39 @@ +/** + * @name Beans sharing similar properties + * @description Beans that share similar properties exhibit unnecessary repetition in the bean + * definitions and make the system's architecture more difficult to see. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/spring/missing-parent-bean + * @tags maintainability + * frameworks/spring + */ +import java +import semmle.code.java.frameworks.spring.Spring + +class MySpringBean extends SpringBean { + int getNumberOfSimilarPropertiesWith(SpringBean other) { + result = count(this.getASimilarPropertyWith(other)) + } + + SpringProperty getASimilarPropertyWith(SpringBean other) { + exists(SpringProperty otherProp | + otherProp = other.getADeclaredProperty() and + result.isSimilar(otherProp) and + result = this.getADeclaredProperty() + ) + } +} + +from MySpringBean bean1, SpringBean bean2, int similarProps +where + similarProps = bean1.getNumberOfSimilarPropertiesWith(bean2) and + similarProps >= 3 and + bean1.getBeanIdentifier() < bean2.getBeanIdentifier() and + bean1 != bean2 +select bean1, + "Bean $@ has " + similarProps.toString() + + " properties similar to $@. Consider introducing a common parent bean for these two beans.", + bean1, bean1.getBeanIdentifier(), + bean2, bean2.getBeanIdentifier() diff --git a/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/MissingParentBean.xml b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/MissingParentBean.xml new file mode 100644 index 00000000000..9e7554e5254 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/MissingParentBean.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/MissingParentBeanGood.xml b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/MissingParentBeanGood.xml new file mode 100644 index 00000000000..bfdf39921cd --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/MissingParentBeanGood.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/TooManyBeans.qhelp b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/TooManyBeans.qhelp new file mode 100644 index 00000000000..69a8e8cec9d --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/TooManyBeans.qhelp @@ -0,0 +1,38 @@ + + + + + +

    +Too many bean definitions in a single file can make the file difficult to understand and maintain. It is also +an indication that the architecture of the system is too tightly coupled and can be refactored. +

    + +
    + +

    +Refactor related bean definitions into separate files, and compose them using the <import/> element. +

    + +
    + + +

    The following example shows a configuration file that imports two other configuration files. These +two files were created by refactoring a file that contained too many bean definitions.

    + + + +
    + + + +
  • +Spring Framework Reference Documentation 3.0: +3.2.2.1 Composing XML-based configuration metadata. +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/TooManyBeans.ql b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/TooManyBeans.ql new file mode 100644 index 00000000000..f22f6634963 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/TooManyBeans.ql @@ -0,0 +1,16 @@ +/** + * @name Too many beans in file + * @description Too many beans in a file can make the file difficult to understand and maintain. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/spring/too-many-beans + * @tags maintainability + * frameworks/spring + */ +import java +import semmle.code.java.frameworks.spring.Spring + +from SpringBeanFile f +where count(f.getABean()) > 40 +select f, "There are too many beans in this file." diff --git a/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/TooManyBeans.xml b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/TooManyBeans.xml new file mode 100644 index 00000000000..bd8b0ad7da1 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/TooManyBeans.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UnusedBean.java b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UnusedBean.java new file mode 100644 index 00000000000..72081b7ee2a --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UnusedBean.java @@ -0,0 +1,12 @@ +class Start { + public static void main(String[] args) { + // Create a context from the XML file, constructing beans + ApplicationContext context = + new ClassPathXmlApplicationContext(new String[] {"services.xml"}); + + // Retrieve the petStore from the context bean factory. + PetStoreService service = context.getBean("petStore", PetStoreService.class); + // Use the value + List userList = service.getUsernameList(); + } +} \ No newline at end of file diff --git a/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UnusedBean.qhelp b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UnusedBean.qhelp new file mode 100644 index 00000000000..1c32e1cf952 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UnusedBean.qhelp @@ -0,0 +1,60 @@ + + + +

    +Bean definitions that are specified but are never used are redundant and can be removed. Unused +beans make the program harder to understand. +

    +

    +A bean definition is considered to be used if one or more of the following is true: +

    +
      +
    • The bean is referenced or defined in the <constructor-arg> or +<property> element of a live bean.
    • +
    • The bean is injected in to a constructor or method of a live bean due to autowiring. This includes +autowiring by annotation (@Autowired or @Inject), and autowiring configured +by the autowired attribute within bean configuration files.
    • +
    • The bean is explictly loaded from a factory bean. It is not always possible to determine when +this occurs, because factory beans are loaded using a String value, which may contain +arbitrary values.
    • +
    • The bean is called reflectively by the Spring framework. For example, if the class is a Spring +MVC framework controller, it may be called in response to web requests.
    • +
    • The bean has a static initializer.
    • +
    • The bean is not lazy, and has a constructor or instance initializer that modifies state outside +of the bean.
    • +
    +

    +Any bean which is not used in one or more ways will be marked as "dead". +

    +
    + +

    +First verify that the bean definition is never used at runtime. In some cases beans may be used in +framework-specific ways, or may be loaded by name from a bean factory in a way that is impossible +to determine statically. +

    +

    +After confirming that the bean is not required, remove the bean. You will also need remove any +references to this bean, which may, in turn, require removing other beans or references. +

    +
    + +

    The following example shows a configuration file that includes two beans:

    + +

    This XML file is loaded with the following Java class:

    + +

    +This class constructs a Spring ApplicationContext using the XML file, then loads the +"petStore" bean. Given these two files, the "clinic" bean will be marked as dead because it is +not used in any context, unlike the "petStore" bean. +

    +
    + +
  • +Spring Framework Reference Documentation 4.2: +6.3 Bean overview. +
  • +
    +
    diff --git a/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UnusedBean.ql b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UnusedBean.ql new file mode 100644 index 00000000000..e66de0097db --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UnusedBean.ql @@ -0,0 +1,233 @@ +/** + * @name Beans that are never used within the code + * @description Beans that are specified but never used are redundant and should be removed. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/spring/unused-bean + * @tags maintainability + * frameworks/spring + */ + +import java +import semmle.code.java.frameworks.spring.Spring + +/** + * A `FieldWrite` that writes to the same instance as the enclosing callable. + */ +class InstanceFieldWrite extends FieldWrite { + InstanceFieldWrite() { + // Must be in an instance callable + not getEnclosingCallable().isStatic() and + // Must be declared in this type or a supertype. + getEnclosingCallable().getDeclaringType().inherits(getField()) and + ( + // There must either be no qualifier - implied "this" + not exists(getQualifier()) or + // Or the qualifier implies we are accessing this or the super type + getQualifier() instanceof ThisAccess or + getQualifier() instanceof SuperAccess + ) + } +} + +/** + * A conservative approximation of statements that may affect state outside the context of the current + * class. + */ +class ImpureStmt extends Stmt { + ImpureStmt() { + exists(Expr e | + e.getEnclosingStmt() = this + | + /* + * Only permit calls to set of whitelisted targets. + */ + ( + e instanceof Call and + not e.(Call).getCallee().getDeclaringType().hasQualifiedName("java.util", "Collections") + ) + or + // Writing to a field that is not an instance field is a no-no + (e instanceof FieldWrite and not e instanceof InstanceFieldWrite) + ) + } +} + +/** + * Get any non-block stmt in the block, including those nested within blocks. + */ +private Stmt getANestedStmt(Block block) { + // Any non-block statement + not result instanceof Block and result = block.getAStmt() or + // Or any statement nested in a block + result = getANestedStmt(block.getAStmt()) +} + + +/** + * A class whose loading and construction by Spring does not have any side-effects outside the class. + * + * This is a conservative approximation. + */ +class SpringPureClass extends Class { + SpringPureClass() { + ( + /* + * The only permitted statement in static initializers is the initialization of a static + * final or effectively final logger fields, or effectively immutable types. + */ + forall(Stmt s | + s = getANestedStmt(getAMember().(StaticInitializer).getBody()) + | + exists(Field f | + f = s.(ExprStmt).getExpr().(AssignExpr).getDest().(FieldWrite).getField() + | + ( + // A logger field + f.getName().toLowerCase() = "logger" or + f.getName().toLowerCase() = "log" or + // An immutable type + f.getType() instanceof ImmutableType + ) and + f.isStatic() and + // Only written to in this statement e.g. final or effectively final + forall(FieldWrite fw | + fw = f.getAnAccess() + | + fw.getEnclosingStmt() = s + ) + ) + ) + ) and + // No constructor, instance initializer or Spring bean init or setter method that is impure. + not exists(Callable c, ImpureStmt impureStmt | + ( + inherits(c.(Method)) or + c = getAMember() + ) and + impureStmt.getEnclosingCallable() = c + | + c instanceof InstanceInitializer or + c instanceof Constructor or + // afterPropertiesSet() method called after bean initialization + c = this.(InitializingBeanClass).getAfterPropertiesSet() or + // Init and setter methods must be pure, because they are called when the bean is initialized + exists(SpringBean bean | + this = bean.getClass() + | + c = bean.getInitMethod() or + c = bean.getAProperty().getSetterMethod() + ) or + // Setter method by autowiring, either in the XML or by annotation + c = this.getAMethod().(SpringBeanAutowiredCallable) or + c = this.getAMethod().(SpringBeanXMLAutowiredSetterMethod) + ) + } +} + +/** + * A Spring class the constructs beans based on the bean identifier. + */ +class SpringBeanFactory extends ClassOrInterface { + SpringBeanFactory() { + getAnAncestor().hasQualifiedName("org.springframework.beans.factory", "BeanFactory") + } + + + /** + * Get a bean constructed by a call to this bean factory. + */ + SpringBean getAConstructedBean() { + exists(Method getBean, MethodAccess call | + getBean.hasName("getBean") and + call.getMethod() = getBean and + getBean.getDeclaringType() = this + | + result.getBeanIdentifier() = call.getArgument(0).(CompileTimeConstantExpr).getStringValue() + ) + } +} + +/** + * A `SpringBean` which is meaningfully used within the program. + * + * A meaningfully used bean cannot be removed without changing the observable behavior of the program. + */ +class LiveSpringBean extends SpringBean { + LiveSpringBean() { + // Must not be needed for side effects due to construction + ( + // Only loaded by the container when required, so construction cannot have any useful side-effects + not isLazyInit() and + // or has no side-effects when constructed + not getClass() instanceof SpringPureClass + ) or + ( + /* + * If the class does not exist for this bean, or the class is not a source bean, then this is + * likely to be a definition using a library class, in which case we should consider it to be + * live. + */ + not exists(getClass()) or + not getClass().fromSource() or + // In alfresco, "webscript" beans should be considered live + getBeanParent*().getBeanParentName() = "webscript" or + // A live child bean implies this bean is live + exists(LiveSpringBean child | this = child.getBeanParent()) or + // Beans constructed by a bean factory are considered live + exists(SpringBeanFactory beanFactory | + this = beanFactory.getAConstructedBean() + ) + ) or + ( + // Referenced by a live bean, either as a property or argument in the XML + exists(LiveSpringBean other | + this = other.getAConstructorArg().getArgRefBean() or + this = other.getAProperty().getPropertyRefBean() + ) or + // Referenced as a factory bean + exists(LiveSpringBean springBean | + this = springBean.getFactoryBean() + ) or + // Injected by @Autowired annotation + exists(SpringBeanAutowiredCallable autowiredCallable | + // The callable must be in a live class + autowiredCallable.getEnclosingSpringBean() instanceof LiveSpringBean or + autowiredCallable.getEnclosingSpringComponent().isLive() + | + // This bean is injected into it + this = autowiredCallable.getAnInjectedBean() + ) or + // Injected by @Autowired annotation on field + exists(SpringBeanAutowiredField autowiredField | + // The field must be in a live class + autowiredField.getEnclosingSpringBean() instanceof LiveSpringBean or + autowiredField.getEnclosingSpringComponent().isLive() + | + // This bean is injected into it + this = autowiredField.getInjectedBean() + ) or + // Injected by autowired specified in XML + exists(SpringBeanXMLAutowiredSetterMethod setterMethod | + // The config method must be on a live bean + setterMethod.getDeclaringType().(SpringBeanRefType).getSpringBean() instanceof LiveSpringBean + | + // This bean is injected into it + this = setterMethod.getInjectedBean() + ) + ) + } +} + +/** + * A `SpringBean` that can be safely removed from the program without changing overall behavior. + */ +class UnusedSpringBean extends SpringBean { + UnusedSpringBean() { + not this instanceof LiveSpringBean + } +} + +from UnusedSpringBean unused +select unused, "The spring bean " + unused.getBeanIdentifier() + " is never used." diff --git a/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UnusedBean.xml b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UnusedBean.xml new file mode 100644 index 00000000000..e40477b83c8 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UnusedBean.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UselessPropertyOverride.qhelp b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UselessPropertyOverride.qhelp new file mode 100644 index 00000000000..376e40ee912 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UselessPropertyOverride.qhelp @@ -0,0 +1,38 @@ + + + + + +

    +A property in a child bean that overrides a property with the same name in its parent and has the same contents +is useless. This is because the bean inherits the property from its parent anyway. +

    + +
    + +

    +If possible, remove the property in the child bean. +

    + +
    + + +

    In the following example, registry is defined in both the parent bean and the child +bean. It should be removed from the child bean.

    + + + +
    + + + +
  • +Spring Framework Reference Documentation 3.0: +3.7 Bean definition inheritance. +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UselessPropertyOverride.ql b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UselessPropertyOverride.ql new file mode 100644 index 00000000000..26d0aebd110 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UselessPropertyOverride.ql @@ -0,0 +1,27 @@ +/** + * @name Useless property override + * @description A bean property that overrides the same property in a parent bean, and has the same + * contents, is useless. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/spring/useless-property-override + * @tags maintainability + * frameworks/spring + */ +import java +import semmle.code.java.frameworks.spring.Spring + +from + SpringBean bean, + SpringProperty prop, + SpringProperty overriddenProp, + SpringBean ancestorBean +where + prop = bean.getADeclaredProperty() and + ancestorBean = bean.getBeanParent+() and + ancestorBean.getADeclaredProperty() = overriddenProp and + overriddenProp.getPropertyName() = prop.getPropertyName() and + prop.isSimilar(overriddenProp) +select prop, "Property overrides $@ in parent, but has the same contents.", + overriddenProp, overriddenProp.toString() diff --git a/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UselessPropertyOverride.xml b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UselessPropertyOverride.xml new file mode 100644 index 00000000000..9337a0f045d --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Architecture/Refactoring Opportunities/UselessPropertyOverride.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/AvoidAutowiring.qhelp b/java/ql/src/Frameworks/Spring/Violations of Best Practice/AvoidAutowiring.qhelp new file mode 100644 index 00000000000..7c124ade08c --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/AvoidAutowiring.qhelp @@ -0,0 +1,78 @@ + + + + + +

    +Using Spring autowiring can make it difficult to see what beans get passed to constructors or setters. The Spring Framework Reference +documentation cites the following disadvantages of autowiring: +

    + +
      +
    • + Explicit dependencies in property and constructor-arg settings always override autowiring. You cannot autowire so-called + simple properties such as primitives, Strings, and + Classes (and arrays of such simple properties). This limitation is by design. +
    • +
    • + Autowiring is less exact than explicit wiring. Although ... Spring is careful to avoid guessing in case of ambiguity that + might have unexpected results, the relationships between your Spring-managed objects are no longer documented explicitly. +
    • +
    • + Wiring information may not be available to tools that may generate documentation from a Spring container. +
    • +
    • + Multiple bean definitions within the container may match the type specified by the setter method or constructor + argument to be autowired. For arrays, collections, or Maps, this is not necessarily a problem. However for + dependencies that expect a single value, this ambiguity is not arbitrarily resolved. If no unique bean definition is available, an exception is thrown. +
    • +
    + +
    + +

    +The Spring Framework Reference documentation suggests the following ways to address problems with autowired beans: +

    +
      +
    • + Abandon autowiring in favor of explicit wiring. +
    • +
    • + Avoid autowiring for a bean definition by setting its autowire-candidate attributes to false. +
    • +
    • + Designate a single bean definition as the primary candidate by setting the primary attribute of its <bean/> element to true. +
    • +
    • + If you are using Java 5 or later, implement the more fine-grained control available with annotation-based configuration. +
    • +
    + + +
    + + +

    The following example shows a bean, autoWiredOrderService, that is defined using +autowiring, and an improved version of the bean, orderService, that is defined using +explicit wiring.

    + + + +
    + + + +
  • +Spring Framework Reference Documentation 3.0: +3.4.5.1 Limitations and disadvantages of autowiring. +
  • +
  • +ONJava: +Twelve Best Practices For Spring XML Configurations. +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/AvoidAutowiring.ql b/java/ql/src/Frameworks/Spring/Violations of Best Practice/AvoidAutowiring.ql new file mode 100644 index 00000000000..a9f6431fc42 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/AvoidAutowiring.ql @@ -0,0 +1,16 @@ +/** + * @name Avoid autowiring + * @description Using autowiring in Spring beans may make it difficult to maintain large projects. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/spring/autowiring + * @tags maintainability + * frameworks/spring + */ +import java +import semmle.code.java.frameworks.spring.Spring + +from SpringBean b +where b.getAutowire() != "no" +select b, "Avoid using autowiring." diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/AvoidAutowiring.xml b/java/ql/src/Frameworks/Spring/Violations of Best Practice/AvoidAutowiring.xml new file mode 100644 index 00000000000..e7f707bdb65 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/AvoidAutowiring.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/DontUseConstructorArgIndex.qhelp b/java/ql/src/Frameworks/Spring/Violations of Best Practice/DontUseConstructorArgIndex.qhelp new file mode 100644 index 00000000000..a782e1069d0 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/DontUseConstructorArgIndex.qhelp @@ -0,0 +1,44 @@ + + + + + +

    +Using type matching instead of index matching in a Spring constructor-arg element produces a more readable bean definition +and is less vulnerable to being broken by a change to the constructor of the bean's underlying class. Index matching +should be used only if type matching is not sufficient to remove ambiguity in the constructor arguments. +

    + +
    + +

    +The bean definition's constructor-arg elements should use type matching instead of index matching. +

    + +
    + + +

    The following example shows a bean, billingService1, whose constructor-arg +elements use index matching, and an improved version of the bean, billingService2, +whose constructor-arg elements use type matching.

    + + + +
    + + + +
  • +Spring Framework Reference Documentation 3.0: +3.4.1.1 Constructor-based dependency injection. +
  • +
  • +ONJava: +Twelve Best Practices For Spring XML Configurations. +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/DontUseConstructorArgIndex.ql b/java/ql/src/Frameworks/Spring/Violations of Best Practice/DontUseConstructorArgIndex.ql new file mode 100644 index 00000000000..7aca9644a9e --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/DontUseConstructorArgIndex.ql @@ -0,0 +1,17 @@ +/** + * @name Use constructor-arg types instead of index + * @description Using a type name instead of an index number in a Spring 'constructor-arg' element + * improves readability. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/spring/constructor-arg-index + * @tags maintainability + * frameworks/spring + */ +import java +import semmle.code.java.frameworks.spring.Spring + +from SpringConstructorArg carg +where carg.hasArgIndex() +select carg, "Use constructor-arg types instead of index." diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/DontUseConstructorArgIndex.xml b/java/ql/src/Frameworks/Spring/Violations of Best Practice/DontUseConstructorArgIndex.xml new file mode 100644 index 00000000000..3e14a36dbce --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/DontUseConstructorArgIndex.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/ImportsFirst.qhelp b/java/ql/src/Frameworks/Spring/Violations of Best Practice/ImportsFirst.qhelp new file mode 100644 index 00000000000..e0aa5a4ca81 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/ImportsFirst.qhelp @@ -0,0 +1,39 @@ + + + + + +

    +Putting import statements at the top of Spring XML bean definition files is good practice because they give a quick summary of the +file's dependencies, and can even be used to document the general architecture of a system. +

    + +
    + +

    +Make sure that all import statements are at the top of the <beans> section of a Spring XML bean definition file. +

    + +
    + + +

    The following example shows a <beans> section of a Spring XML bean definition file in +which an import statement is in the middle, and a <beans> section in +which all the import statements are at the top.

    + + + +
    + + + +
  • +Spring Framework Reference Documentation 3.0: +3.2.2.1 Composing XML-based configuration metadata. +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/ImportsFirst.ql b/java/ql/src/Frameworks/Spring/Violations of Best Practice/ImportsFirst.ql new file mode 100644 index 00000000000..14b0c6ce189 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/ImportsFirst.ql @@ -0,0 +1,22 @@ +/** + * @name Imports should come before bean definitions + * @description Putting 'import' statements before bean definitions in a Spring bean configuration + * file makes it easier to immediately see all the file's dependencies. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/spring/import-location + * @tags maintainability + * frameworks/spring + */ +import java + +import semmle.code.java.frameworks.spring.Spring + +from SpringImport i +where + exists(SpringBean b | + i.getSpringBeanFile() = b.getSpringBeanFile() and + i.getLocation().getStartLine() > b.getLocation().getStartLine() + ) +select i, "Imports should come before bean definitions." diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/ImportsFirst.xml b/java/ql/src/Frameworks/Spring/Violations of Best Practice/ImportsFirst.xml new file mode 100644 index 00000000000..739e375f28c --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/ImportsFirst.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/NoBeanDescription.qhelp b/java/ql/src/Frameworks/Spring/Violations of Best Practice/NoBeanDescription.qhelp new file mode 100644 index 00000000000..c4cc875b6b3 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/NoBeanDescription.qhelp @@ -0,0 +1,39 @@ + + + + + +

    +In a Spring XML bean definition file, adding a <description> element to a <bean> element or the enclosing <beans> +element to document the purpose of the bean specification is good practice. A description element also has the advantage of making it easier for +tools to detect and display the documentation for your bean specifications. +

    + +
    + +

    +Add a <description> element either in the <bean> element or its enclosing <beans> element. +

    + +
    + + +

    The following example shows a Spring XML bean definition file that includes +<description> elements.

    + + + +
    + + + +
  • +ONJava: +Twelve Best Practices For Spring XML Configurations. +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/NoBeanDescription.ql b/java/ql/src/Frameworks/Spring/Violations of Best Practice/NoBeanDescription.ql new file mode 100644 index 00000000000..6b2517997d6 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/NoBeanDescription.ql @@ -0,0 +1,15 @@ +/** + * @name This bean does not have a description element + * @description Adding 'description' elements to a Spring XML bean definition file is good practice. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/spring/missing-bean-description + * @tags maintainability + */ +import java +import semmle.code.java.frameworks.spring.Spring + +from SpringBean b +where not exists(SpringDescription d | d = b.getASpringChild() or d = b.getSpringBeanFile().getBeansElement().getAChild()) +select b, "This bean does not have a description." diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/NoBeanDescription.xml b/java/ql/src/Frameworks/Spring/Violations of Best Practice/NoBeanDescription.xml new file mode 100644 index 00000000000..438d11b4449 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/NoBeanDescription.xml @@ -0,0 +1,27 @@ + + + + This file configures the various service beans. + + + + + + This bean defines base properties common to the service beans + + ... + + + + ... + + + + ... + + \ No newline at end of file diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/ParentShouldNotUseAbstractClass.qhelp b/java/ql/src/Frameworks/Spring/Violations of Best Practice/ParentShouldNotUseAbstractClass.qhelp new file mode 100644 index 00000000000..8167b3cb9c1 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/ParentShouldNotUseAbstractClass.qhelp @@ -0,0 +1,49 @@ + + + + + +

    +A non-abstract Spring bean that is a parent of other beans must not specify an abstract class. Doing so causes an error during +bean instantiation. +

    + +
    + +

    +Make sure that a non-abstract bean does not specify an abstract class, by +doing one of the following:

    + +
      +
    • Specify that the bean is also abstract by adding abstract="true" to the bean specification.
    • +
    • If possible, update the class that is specified by the bean so that it is not abstract.
    • +
    + +

    You can also make the XML parent bean definition abstract and remove any references +from it to any class (in which case it becomes a pure bean template). Note that, like an +abstract class, an abstract bean cannot be used on its own and only +provides property and constructor definitions to its children. +

    + +
    + + +

    In the following example, the bean wrongConnectionPool is using an abstract class, +ConnectionPool, which causes an error. Instead, the bean should be declared +abstract, as shown in the definition of connectionPool.

    + + + +
    + + + +
  • +Spring Framework Reference Documentation 3.0: 3.7 Bean definition inheritance. +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/ParentShouldNotUseAbstractClass.ql b/java/ql/src/Frameworks/Spring/Violations of Best Practice/ParentShouldNotUseAbstractClass.ql new file mode 100644 index 00000000000..c40ee2aabd2 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/ParentShouldNotUseAbstractClass.ql @@ -0,0 +1,32 @@ +/** + * @name Non-abstract parent beans should not use an abstract class + * @description A non-abstract Spring bean that is a parent of other beans and specifies an + * abstract class causes an error during bean instantiation. + * @kind problem + * @problem.severity error + * @precision low + * @id java/spring/parent-bean-abstract-class + * @tags reliability + * maintainability + * frameworks/spring + */ +import java + +import semmle.code.java.frameworks.spring.Spring + +class ParentBean extends SpringBean { + ParentBean() { + exists(SpringBean b | b.getBeanParent() = this) + } + + RefType getDeclaredClass() { + result = this.getClass() and + this.hasAttribute("class") and + not this.getAttribute("abstract").getValue() = "true" + } +} + +from ParentBean parent +where parent.getDeclaredClass().isAbstract() +select parent, "Parent bean $@ should not have an abstract class.", + parent, parent.getBeanIdentifier() diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/ParentShouldNotUseAbstractClass.xml b/java/ql/src/Frameworks/Spring/Violations of Best Practice/ParentShouldNotUseAbstractClass.xml new file mode 100644 index 00000000000..92318c13d33 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/ParentShouldNotUseAbstractClass.xml @@ -0,0 +1,14 @@ + + + + + + + + + \ No newline at end of file diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseIdInsteadOfName.qhelp b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseIdInsteadOfName.qhelp new file mode 100644 index 00000000000..bd3376d3b92 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseIdInsteadOfName.qhelp @@ -0,0 +1,44 @@ + + + + + +

    +To name a Spring bean, it is best to use the id attribute instead of the name attribute. Using the +id attribute enables the XML parser to perform additional checks (for example, checking if the id in a ref +attribute is an actual id of an XML element). +

    + +
    + +

    +Use the id attribute instead of the name attribute when naming a bean. +

    + +
    + + +

    In the following example, the dao bean is shown using the name attribute, +which allows a typo to go undetected because the XML parser does not check name. In contrast, +using the id attribute allows the XML parser to catch the typo.

    + + + +
    + + + +
  • +Spring Framework Reference Documentation 3.0: +3.3.1 Naming beans. +
  • +
  • +W3C: +3.3.1 Attribute Types. +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseIdInsteadOfName.ql b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseIdInsteadOfName.ql new file mode 100644 index 00000000000..fb5ffbdc90e --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseIdInsteadOfName.ql @@ -0,0 +1,20 @@ +/** + * @name Use id instead of name + * @description Using 'id' instead of 'name' to name a Spring bean enables the XML parser to perform + * additional checks. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/spring/bean-id + * @tags reliability + * maintainability + * frameworks/spring + */ +import java +import semmle.code.java.frameworks.spring.Spring + +from SpringBean b +where + not b.hasBeanId() and + b.hasBeanName() +select b, "Use \"id\" instead of \"name\" to take advantage of the IDREF constraint in the XML parser." diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseIdInsteadOfName.xml b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseIdInsteadOfName.xml new file mode 100644 index 00000000000..af75a2d37dd --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseIdInsteadOfName.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseLocalRef.qhelp b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseLocalRef.qhelp new file mode 100644 index 00000000000..81a78df3632 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseLocalRef.qhelp @@ -0,0 +1,58 @@ + + + + + +

    +If at all possible, refer to Spring beans in the same XML file using local references, that is <idref local="targetBean">. This +requires that the bean being referenced is in the same XML file, and is named using the id attribute. Using local references +has the advantage of allowing reference errors to be detected during XML parsing, instead of during deployment or instantiation. +

    + +

    +From the Spring Framework Reference documentation on idref elements:

    + +
    +

    +[Using the idref tag in a property element] is preferable to [using the bean name in the property's value attribute], because using the idref +tag allows the container to validate at deployment time that the referenced, named bean actually exists. In the second variation, no validation +is performed on the value that is passed to the [name] property of the client bean. Typos are only discovered (with most likely fatal results) +when the client bean is actually instantiated. If the client bean is a prototype bean, this typo and the resulting exception may only be +discovered long after the container is deployed. +

    +

    +Additionally, if the referenced bean is in the same XML unit, and the bean name is the bean id, you can use the local attribute, which allows +the XML parser itself to validate the bean id earlier, at XML document parse time. +

    +
    + +
    + +

    +Use a local idref when referring to beans in the same XML file. This allows errors to be detected earlier, at XML parse time +rather than during instantiation. +

    + +
    + + +

    In the following example, the shippingService bean is shown using the ref element, +which cannot be checked by the XML parser. The orderService bean is shown using the +idref element, which allows the XML parser to find any errors at parse time.

    + + + +
    + + + +
  • +Spring Framework Reference Documentation 3.0: +3.4.2.1 Straight values (primitives, Strings, and so on). +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseLocalRef.ql b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseLocalRef.ql new file mode 100644 index 00000000000..5dd56b9429a --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseLocalRef.ql @@ -0,0 +1,24 @@ +/** + * @name Use local refs when referring to beans in the same file + * @description Using local references when referring to Spring beans in the same file allows + * reference errors to be detected during XML parsing. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/spring/non-local-reference + * @tags reliability + * maintainability + * frameworks/spring + */ +import java +import semmle.code.java.frameworks.spring.Spring + +from SpringAbstractRef ref, SpringBean refBean, SpringBean referencedBean +where + refBean = ref.getEnclosingBean() and + referencedBean = ref.getBean() and + referencedBean.getSpringBeanFile() = refBean.getSpringBeanFile() and + not ref.hasBeanLocalName() +select ref, + "Non-local reference points to bean $@ in the same file. Use a local reference instead.", + referencedBean, referencedBean.getBeanIdentifier() diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseLocalRef.xml b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseLocalRef.xml new file mode 100644 index 00000000000..b2dcdea2119 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseLocalRef.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjection.java b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjection.java new file mode 100644 index 00000000000..d5cf0de1ea6 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjection.java @@ -0,0 +1,22 @@ +// Class for bean 'chart1' +public class WrongChartMaker { + private AxisRenderer axisRenderer = new DefaultAxisRenderer(); + private TrendRenderer trendRenderer = new DefaultTrendRenderer(); + + public WrongChartMaker() {} + + // Each combination of the optional parameters must be represented by a constructor. + public WrongChartMaker(AxisRenderer customAxisRenderer) { + this.axisRenderer = customAxisRenderer; + } + + public WrongChartMaker(TrendRenderer customTrendRenderer) { + this.trendRenderer = customTrendRenderer; + } + + public WrongChartMaker(AxisRenderer customAxisRenderer, + TrendRenderer customTrendRenderer) { + this.axisRenderer = customAxisRenderer; + this.trendRenderer = customTrendRenderer; + } +} \ No newline at end of file diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjection.qhelp b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjection.qhelp new file mode 100644 index 00000000000..0f209b4571a --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjection.qhelp @@ -0,0 +1,68 @@ + + + + + +

    +When you use the Spring Framework, using setter injection instead of constructor injection is more flexible, particularly for Spring beans with a large number of optional properties. +Constructor injection should be used only on required bean properties; using constructor injection on optional bean properties requires a large number of constructors +to handle different combinations of properties. +

    + +

    +Although the generally accepted best practice is to use constructor injection for mandatory dependencies, and setter injection for optional dependencies, +the @Required annotation allows you to forgo constructor injection completely. Using the @Required annotation +on a setter method makes the framework check that a dependency is injected using that method. +

    + +
    + +

    +Use setter injection in bean configurations, marking required properties with the @Required annotation. It makes it easier to +accommodate a large number of optional properties, and makes the bean more flexible by allowing for re-injection of dependencies. +

    + +
    + + +

    The following example shows a bean that is defined using constructor injection. The bean +configuration is followed by the class definition.

    + + + + + +

    The following example shows how the same bean can be defined using setter injection instead. Again, +the bean configuration is followed by the class definition.

    + + + + + +
    + + + +
  • +Martin Fowler: +Inversion of Control Containers and the Dependency Injection pattern. +
  • +
  • +ONJava: +Twelve Best Practices for Spring XML Configurations. +
  • +
  • +Spring Framework Reference Documentation 3.0: +3.4.1.1 Constructor-based dependency injection, +3.4.1.2 Setter-based dependency injection. +
  • +
  • +SpringSource: +Setter injection versus constructor injection and the use of @Required. +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjection.ql b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjection.ql new file mode 100644 index 00000000000..2b1edc22351 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjection.ql @@ -0,0 +1,17 @@ +/** + * @name Use setter injection instead of constructor injection + * @description When using the Spring Framework, using setter injection instead of constructor + * injection is more flexible, especially when several properties are optional. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/spring/constructor-injection + * @tags maintainability + * frameworks/spring + */ +import java +import semmle.code.java.frameworks.spring.Spring + +from SpringBean b +where exists(SpringConstructorArg carg | b.getASpringChild() = carg) +select b, "Use setter injection instead of constructor injection." diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjection.xml b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjection.xml new file mode 100644 index 00000000000..d249a8079b7 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjection.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjectionGood.java b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjectionGood.java new file mode 100644 index 00000000000..7862c33f99f --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjectionGood.java @@ -0,0 +1,15 @@ +// Class for bean 'chart2' +public class ChartMaker { + private AxisRenderer axisRenderer = new DefaultAxisRenderer(); + private TrendRenderer trendRenderer = new DefaultTrendRenderer(); + + public ChartMaker() {} + + public void setAxisRenderer(AxisRenderer axisRenderer) { + this.axisRenderer = axisRenderer; + } + + public void setTrendRenderer(TrendRenderer trendRenderer) { + this.trendRenderer = trendRenderer; + } +} \ No newline at end of file diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjectionGood.xml b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjectionGood.xml new file mode 100644 index 00000000000..a132f79bfef --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseSetterInjectionGood.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseShortcutForms.qhelp b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseShortcutForms.qhelp new file mode 100644 index 00000000000..721ac7a7401 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseShortcutForms.qhelp @@ -0,0 +1,46 @@ + + + + + +

    +Shortcut forms, introduced in Spring 1.2, allow nested value elements to instead be defined as attributes in the enclosing +property entry. This leads to shorter XML bean configurations that are easier to read. +

    + +
    + +

    +When possible, use the shortcut form for defining bean property values. +

    + +

    Note that this does not apply to idref elements, which are the preferred form of referring to another bean. These do not have a shortcut form +that can still be checked by the XML parser. +

    + +
    + + +

    The following example shows how a bean that is defined using shortcut forms is more concise than +the same bean defined using nested value elements.

    + + + +
    + + + +
  • +ONJava: +Twelve Best Practices for Spring XML Configurations. +
  • +
  • +Spring Framework Reference Documentation 3.0: +3.4.2.1 Straight values (primitives, Strings, and so on). +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseShortcutForms.ql b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseShortcutForms.ql new file mode 100644 index 00000000000..97dab395426 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseShortcutForms.ql @@ -0,0 +1,58 @@ +/** + * @name Use shortcut forms for values + * @description Using shortcut forms may make a Spring XML configuration file less cluttered. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/spring/non-shortcut-form + * @tags maintainability + * frameworks/spring + */ +import java +import semmle.code.java.frameworks.spring.Spring + +class SpringConstructorArgUseShortcut extends SpringConstructorArg { + SpringConstructorArgUseShortcut() { + not this.hasArgValueString() and + exists(SpringValue val | val = this.getASpringChild()) + } + + string getMessage() { + not this.hasArgValueString() and + exists(SpringValue val | val = this.getASpringChild()) and + result = "Use the shortcut \"value\" attribute instead of a nested element." + } +} + +class SpringEntryUseShortcut extends SpringEntry { + SpringEntryUseShortcut() { + not this.hasValueStringRaw() and + exists(SpringValue val | val = this.getASpringChild()) + } + + string getMessage() { + not this.hasValueStringRaw() and + exists(SpringValue val | val = this.getASpringChild()) and + result = "Use the shortcut \"value\" attribute instead of a nested element." + } +} + +class SpringPropertyUseShortcut extends SpringProperty { + SpringPropertyUseShortcut() { + not this.hasPropertyValueString() and + exists(SpringValue val | val = this.getASpringChild()) + } + + string getMessage() { + not this.hasPropertyValueString() and + exists(SpringValue val | val = this.getASpringChild()) and + result = "Use the shortcut \"value\" attribute instead of a nested element." + } +} + +from SpringXMLElement springElement, string msg +where + exists(SpringConstructorArgUseShortcut cons | cons = springElement and msg = cons.getMessage()) or + exists(SpringEntryUseShortcut entry | entry = springElement and msg = entry.getMessage()) or + exists(SpringPropertyUseShortcut prop | prop = springElement and msg = prop.getMessage()) +select springElement, msg diff --git a/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseShortcutForms.xml b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseShortcutForms.xml new file mode 100644 index 00000000000..703e67ffe93 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/Violations of Best Practice/UseShortcutForms.xml @@ -0,0 +1,38 @@ + + + + main_service_registry + + + Top-level registry for services + + + + + + orderService + + com.foo.bar.OrderService + + + + billingService + + com.foo.bar.BillingService + + + + + + + + + + + + + + + + + diff --git a/java/ql/src/Frameworks/Spring/XML Configuration Errors/MissingSetters.java b/java/ql/src/Frameworks/Spring/XML Configuration Errors/MissingSetters.java new file mode 100644 index 00000000000..6a310cfb758 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/XML Configuration Errors/MissingSetters.java @@ -0,0 +1,9 @@ +// bean class +public class ContentService { + private TransactionHelper helper; + + // This method does not match the property in the bean file. + public void setHelper(TransactionHelper helper) { + this.helper = helper; + } +} diff --git a/java/ql/src/Frameworks/Spring/XML Configuration Errors/MissingSetters.qhelp b/java/ql/src/Frameworks/Spring/XML Configuration Errors/MissingSetters.qhelp new file mode 100644 index 00000000000..d37d0948ed4 --- /dev/null +++ b/java/ql/src/Frameworks/Spring/XML Configuration Errors/MissingSetters.qhelp @@ -0,0 +1,43 @@ + + + + + +

    +The absence of a matching setter method for a property that is defined in a Spring XML bean causes a validation error when the project is compiled. +

    + +
    + +

    +Ensure that there is a setter method in the bean file that matches the property name. +

    + +
    + + +

    The following example shows a bean file in which there is no match for the setter method that is +in the class.

    + + + +

    This is the bean class.

    + + + +

    The property transactionHelper should instead have the name helper.

    + +
    + + + +
  • +Spring Framework Reference Documentation 3.0: +3.4.1.2 Setter-based dependency injection. +
  • + + +
    +
    diff --git a/java/ql/src/Frameworks/Spring/XML Configuration Errors/MissingSetters.ql b/java/ql/src/Frameworks/Spring/XML Configuration Errors/MissingSetters.ql new file mode 100644 index 00000000000..f357997ab3a --- /dev/null +++ b/java/ql/src/Frameworks/Spring/XML Configuration Errors/MissingSetters.ql @@ -0,0 +1,21 @@ +/** + * @name Missing setters for property dependency injection + * @description Not declaring a setter for a property that is defined in a Spring XML file causes a + * compilation error. + * @kind problem + * @problem.severity error + * @precision low + * @id java/spring/missing-setter + * @tags reliability + * maintainability + * frameworks/spring + */ +import java +import semmle.code.java.frameworks.spring.Spring + +from SpringProperty p +where + not p.getEnclosingBean().isAbstract() and + not exists(p.getSetterMethod()) +select p, "This property is missing a setter method on $@.", + p.getEnclosingBean().getClass() as c, c.getName() diff --git a/java/ql/src/Frameworks/Spring/XML Configuration Errors/MissingSetters.xml b/java/ql/src/Frameworks/Spring/XML Configuration Errors/MissingSetters.xml new file mode 100644 index 00000000000..9a4ac59115c --- /dev/null +++ b/java/ql/src/Frameworks/Spring/XML Configuration Errors/MissingSetters.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/java/ql/src/Language Abuse/CastThisToTypeParameter.java b/java/ql/src/Language Abuse/CastThisToTypeParameter.java new file mode 100644 index 00000000000..707cee38a6d --- /dev/null +++ b/java/ql/src/Language Abuse/CastThisToTypeParameter.java @@ -0,0 +1,14 @@ +public class CastThisToTypeParameter { + private abstract static class BadBaseNode> { + public abstract T getParent(); + + public T getRoot() { + // BAD: relies on derived types to use the right pattern + T cur = (T)this; + while(cur.getParent() != null) { + cur = cur.getParent(); + } + return cur; + } + } +} \ No newline at end of file diff --git a/java/ql/src/Language Abuse/CastThisToTypeParameter.qhelp b/java/ql/src/Language Abuse/CastThisToTypeParameter.qhelp new file mode 100644 index 00000000000..357850049a5 --- /dev/null +++ b/java/ql/src/Language Abuse/CastThisToTypeParameter.qhelp @@ -0,0 +1,29 @@ + + + +

    +Casting this to a type parameter usually suggests that there is an implicit type constraint - the programmer probably wanted to express +the notion that this could be converted to the type parameter (when using the enclosing method from derived types). +However, casting to the desired type, relies on derived types to ensure that the cast will succeed without the compiler forcing them +to do so. +

    + +
    + + +

    +The solution is to enforce the constraint by adding an abstract method on the base type (see example below). Each derived +type must then implement this method, which makes the constraint checkable by the compiler and removes the need for a cast. +

    + +
    + +

    In this example BadBaseNode relies on derived types to use the right pattern.

    + +

    This constraint is better enforced by adding an abstract method on the base type. Implementing +this method makes the constraint checkable by the compiler.

    + +
    +
    diff --git a/java/ql/src/Language Abuse/CastThisToTypeParameter.ql b/java/ql/src/Language Abuse/CastThisToTypeParameter.ql new file mode 100644 index 00000000000..11a813bc449 --- /dev/null +++ b/java/ql/src/Language Abuse/CastThisToTypeParameter.ql @@ -0,0 +1,27 @@ +/** + * @name Cast of 'this' to a type parameter + * @description Casting 'this' to a type parameter of the current type masks an implicit type constraint that should be explicitly stated. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/cast-of-this-to-type-parameter + * @tags reliability + * maintainability + * language-features + */ + +import java + +from Expr e, GenericType src, TypeVariable dest +where + exists(CastExpr cse | + cse = e and + exists(cse.getLocation()) and + cse.getExpr() instanceof ThisAccess and + src = cse.getExpr().getType() and + dest = cse.getType() + ) and + dest.getGenericType() = src +select e, "Casting 'this' to $@, a type parameter of $@, masks an implicit type constraint that should be explicitly stated.", + dest, dest.getName(), + src, src.getName() diff --git a/java/ql/src/Language Abuse/CastThisToTypeParameterFix.java b/java/ql/src/Language Abuse/CastThisToTypeParameterFix.java new file mode 100644 index 00000000000..3158db47f95 --- /dev/null +++ b/java/ql/src/Language Abuse/CastThisToTypeParameterFix.java @@ -0,0 +1,52 @@ +public class CastThisToTypeParameter { + private abstract static class GoodBaseNode> { + public abstract T getSelf(); + public abstract T getParent(); + + public T getRoot() { + // GOOD: introduce an abstract method to enforce the constraint + // that 'this' can be converted to T for derived types + T cur = getSelf(); + while(cur.getParent() != null) + { + cur = cur.getParent(); + } + return cur; + } + } + + private static class GoodConcreteNode extends GoodBaseNode { + private String name; + private GoodConcreteNode parent; + + public GoodConcreteNode(String name, GoodConcreteNode parent) + { + this.name = name; + this.parent = parent; + } + + @Override + public GoodConcreteNode getSelf() { + return this; + } + + @Override + public GoodConcreteNode getParent() { + return parent; + } + + @Override + public String toString() { + return name; + } + } + + public static void main(String[] args) { + GoodConcreteNode a = new GoodConcreteNode("a", null); + GoodConcreteNode b = new GoodConcreteNode("b", a); + GoodConcreteNode c = new GoodConcreteNode("c", a); + GoodConcreteNode d = new GoodConcreteNode("d", b); + GoodConcreteNode root = d.getRoot(); + System.out.println(a + " " + root); + } +} \ No newline at end of file diff --git a/java/ql/src/Language Abuse/ChainedInstanceof.java b/java/ql/src/Language Abuse/ChainedInstanceof.java new file mode 100644 index 00000000000..1be4773a339 --- /dev/null +++ b/java/ql/src/Language Abuse/ChainedInstanceof.java @@ -0,0 +1,65 @@ +import java.util.*; + +public class ChainedInstanceof { + public static void main(String[] args) { + // BAD: example of a sequence of type tests + List badAnimals = new ArrayList(); + badAnimals.add(new BadCat()); + badAnimals.add(new BadDog()); + for(BadAnimal a: badAnimals) { + if(a instanceof BadCat) System.out.println("Miaow!"); + else if(a instanceof BadDog) System.out.println("Woof!"); + else throw new RuntimeException("Oops!"); + } + + // GOOD: solution using polymorphism + List polymorphicAnimals = new ArrayList(); + polymorphicAnimals.add(new PolymorphicCat()); + polymorphicAnimals.add(new PolymorphicDog()); + for(PolymorphicAnimal a: polymorphicAnimals) a.speak(); + + // GOOD: solution using the visitor pattern + List visitableAnimals = new ArrayList(); + visitableAnimals.add(new VisitableCat()); + visitableAnimals.add(new VisitableDog()); + for(VisitableAnimal a: visitableAnimals) a.accept(new SpeakVisitor()); + } + + //#################### TYPES FOR BAD EXAMPLE #################### + + private interface BadAnimal {} + private static class BadCat implements BadAnimal {} + private static class BadDog implements BadAnimal {} + + //#################### TYPES FOR POLYMORPHIC EXAMPLE #################### + + private interface PolymorphicAnimal { + void speak(); + } + private static class PolymorphicCat implements PolymorphicAnimal { + public void speak() { System.out.println("Miaow!"); } + } + private static class PolymorphicDog implements PolymorphicAnimal { + public void speak() { System.out.println("Woof!"); } + } + + //#################### TYPES FOR VISITOR EXAMPLE #################### + + private interface Visitor { + void visit(VisitableCat c); + void visit(VisitableDog d); + } + private static class SpeakVisitor implements Visitor { + public void visit(VisitableCat c) { System.out.println("Miaow!"); } + public void visit(VisitableDog d) { System.out.println("Woof!"); } + } + private interface VisitableAnimal { + void accept(Visitor v); + } + private static class VisitableCat implements VisitableAnimal { + public void accept(Visitor v) { v.visit(this); } + } + private static class VisitableDog implements VisitableAnimal { + public void accept(Visitor v) { v.visit(this); } + } +} diff --git a/java/ql/src/Language Abuse/ChainedInstanceof.qhelp b/java/ql/src/Language Abuse/ChainedInstanceof.qhelp new file mode 100644 index 00000000000..a6811b58473 --- /dev/null +++ b/java/ql/src/Language Abuse/ChainedInstanceof.qhelp @@ -0,0 +1,83 @@ + + + +

    +Long sequences of type tests are often used to dispatch control to different branches of the code based on the type +of a variable, as shown in the example below. They are often used to simulate pattern-matching in languages that do +not support it. Whilst this works as a dispatch method, there are a number of problems: +

    + +
      +
    • +They are difficult to maintain. It is easy to add a new subtype and forget to modify all of the type test sequences +throughout your code. +
    • + +
    • +They introduce unwanted dependencies on concrete classes. Code cannot be written only in terms of an interface but +must instead be written considering all of the different special cases. +
    • + +
    • +They can be error-prone - it is easy to test for a base type before a derived type, resulting in a failure to +execute the code handling the derived type. +
    • + +
    + +
    + + +

    +There are a number of different possible solutions to this problem: +

    + +
      +
    • +Polymorphism. You can add a virtual method to the type hierarchy and put the segments of code to be called +in the relevant override for each concrete class. This is a good solution when: (a) you can change the type hierarchy +and (b) the operation being implemented is core functionality that the types should implement. If you implement this +solution then you must be careful not to introduce unwanted dependencies. If the operation depends on entities +that themselves depend on the type hierarchy, then you cannot move the operation to the type hierarchy without creating a dependency cycle. +
    • + +
    • +The visitor pattern. You can introduce a visitor interface containing a visit method for each type in +the type hierarchy, and add an accept method to each type in the hierarchy that takes a visitor as its parameter. +The accept method calls the visit method of the visitor on this. Concrete visitors then implement the +interface and process each specific type as necessary. This is a good solution when: (a) you can change the type hierarchy +and (b) the type hierarchy should not know about the operation being implemented (either to avoid dependency +or because it is not core functionality for the types in the hierarchy). It is also useful when you want to provide +multiple operations with the same structure, on the same set of types, and you want the types themselves to control the way +that the operation is structured. For example, "visit this tree using an in-order walk and apply the operation to each +node". The basic visitor pattern is not suitable for all situations because it is cyclically-dependent, and the infrastructure involved +is comparatively heavyweight. +
    • + +
    • +Reflection. You can look up one of a set of overloaded methods based on the type of one of the method +parameters and invoke the method manually. This results in a loss of +type safety and is rather untidy, but there are times when it is the best solution. In particular, reflection is useful when you +cannot change the type hierarchy, for example, because it is third-party code. +
    • +
    + +
    + +

    The following example demonstrates the use "Polymorphism" and "The visitor pattern". More details on reflection can be found in [Flanagan].

    + + + +
    + + + +
  • D. Flanagan, Java in a Nutshell: A Desktop Quick Reference. + O'Reilly Media, 1997.
  • +
  • E. Gamma, R. Helm, R. Johnson, J. Vlissides, Design patterns: elements of reusable + object-oriented software. Addison-Wesley Longman Publishing Co., Inc. Boston, MA, 1995.
  • + +
    +
    diff --git a/java/ql/src/Language Abuse/ChainedInstanceof.ql b/java/ql/src/Language Abuse/ChainedInstanceof.ql new file mode 100644 index 00000000000..c53660df26d --- /dev/null +++ b/java/ql/src/Language Abuse/ChainedInstanceof.ql @@ -0,0 +1,39 @@ +/** + * @name Chain of 'instanceof' tests + * @description Long sequences of type tests on a variable are difficult to maintain. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/chained-type-tests + * @tags maintainability + * language-features + */ + +import java + +int instanceofCountForIfChain(IfStmt is) { + exists(int rest | + ( + if is.getElse() instanceof IfStmt then + rest = instanceofCountForIfChain(is.getElse()) + else + rest = 0 + ) + and + ( + if is.getCondition() instanceof InstanceOfExpr then + result = 1 + rest + else + result = rest + ) + ) +} + +from IfStmt is, int n +where + n = instanceofCountForIfChain(is) and + n > 5 and + not exists(IfStmt other | is = other.getElse()) +select is, + "This if block performs a chain of " + n + + " type tests - consider alternatives, e.g. polymorphism or the visitor pattern." diff --git a/java/ql/src/Language Abuse/DubiousDowncastOfThis.java b/java/ql/src/Language Abuse/DubiousDowncastOfThis.java new file mode 100644 index 00000000000..bbd84de856b --- /dev/null +++ b/java/ql/src/Language Abuse/DubiousDowncastOfThis.java @@ -0,0 +1,20 @@ +public class DubiousDowncastOfThis { + private static class BadBase { + private Derived d; + + public BadBase(Derived d) { + if(d != null && this instanceof Derived) + this.d = (Derived)this; // violation + else + this.d = d; + } + } + + private static class Derived extends BadBase { + public Derived() { + super(null); + } + } + + public static void main(String[] args) {} +} \ No newline at end of file diff --git a/java/ql/src/Language Abuse/DubiousDowncastOfThis.qhelp b/java/ql/src/Language Abuse/DubiousDowncastOfThis.qhelp new file mode 100644 index 00000000000..fb0bafed452 --- /dev/null +++ b/java/ql/src/Language Abuse/DubiousDowncastOfThis.qhelp @@ -0,0 +1,29 @@ + + + +

    +Downcasting this to a derived type creates a dependency cycle. Derived types already depend +on their parent type and the cast creates a dependency in the other direction. +

    + +

    Dependency cycles should be avoided as they make code both difficult to read and difficult to test. +In addition, a type should not know about its specific descendants, even though it may impose some +constraints on them as a group (for example, abstract classes may require every derived type to implement +a method with a specific signature).

    + +
    + +

    +The base and derived types should be redesigned so that there is no need for the base type to depend +on the types deriving from it.

    + +
    + +

    In this example, BadBase introduces a dependency cycle with Derived by +coercing the type of this to a derived type.

    + + +
    +
    diff --git a/java/ql/src/Language Abuse/DubiousDowncastOfThis.ql b/java/ql/src/Language Abuse/DubiousDowncastOfThis.ql new file mode 100644 index 00000000000..87e19749f2e --- /dev/null +++ b/java/ql/src/Language Abuse/DubiousDowncastOfThis.ql @@ -0,0 +1,29 @@ +/** + * @name Dubious downcast of 'this' + * @description Casting 'this' to a derived type introduces a dependency cycle + * between the type of 'this' and the target type. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/downcast-of-this + * @tags testability + * maintainability + * language-features + */ + +import java + +from Expr e, RefType src, RefType dest +where + exists(CastExpr cse | cse = e | + exists(cse.getLocation()) and + cse.getExpr() instanceof ThisAccess and + src = cse.getExpr().getType() and + dest = cse.getType() + ) and + src.hasSubtype*(dest) and + src != dest and + not dest instanceof TypeVariable +select e, "Downcasting 'this' from $@ to $@ introduces a dependency cycle between the two types.", + src, src.getName(), + dest, dest.getName() diff --git a/java/ql/src/Language Abuse/DubiousTypeTestOfThis.java b/java/ql/src/Language Abuse/DubiousTypeTestOfThis.java new file mode 100644 index 00000000000..9ade8feb00a --- /dev/null +++ b/java/ql/src/Language Abuse/DubiousTypeTestOfThis.java @@ -0,0 +1,20 @@ +public class DubiousTypeTestOfThis { + private static class BadBase { + private Derived d; + + public BadBase(Derived d) { + if(d != null && this instanceof Derived) // violation + this.d = (Derived)this; + else + this.d = d; + } + } + + private static class Derived extends BadBase { + public Derived() { + super(null); + } + } + + public static void main(String[] args) {} +} \ No newline at end of file diff --git a/java/ql/src/Language Abuse/DubiousTypeTestOfThis.qhelp b/java/ql/src/Language Abuse/DubiousTypeTestOfThis.qhelp new file mode 100644 index 00000000000..4a74c9ba7fc --- /dev/null +++ b/java/ql/src/Language Abuse/DubiousTypeTestOfThis.qhelp @@ -0,0 +1,30 @@ + + + +

    +Testing whether this is an instance of a derived type creates a dependency cycle. +Derived types already depend on their parent type and the cast creates a dependency in the other direction. +

    + +

    Dependency cycles should be avoided as they make code both difficult to read and difficult to test. +In addition, a type should not know about its specific descendants, even though it may impose some +constraints on them as a group (for example, the need for every derived type to implement a method +with a specific signature).

    + +
    + + +

    +The base and derived types should be redesigned so that there is no need for the base type to depend +on the types deriving from it.

    + +
    + +

    In this example, BadBase introduces a dependency cycle with Derived by +testing the type of this.

    + + +
    +
    diff --git a/java/ql/src/Language Abuse/DubiousTypeTestOfThis.ql b/java/ql/src/Language Abuse/DubiousTypeTestOfThis.ql new file mode 100644 index 00000000000..a26e77355e2 --- /dev/null +++ b/java/ql/src/Language Abuse/DubiousTypeTestOfThis.ql @@ -0,0 +1,24 @@ +/** + * @name Dubious type test of 'this' + * @description Testing whether 'this' is an instance of a derived type introduces + * a dependency cycle between the type of 'this' and the target type. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/type-test-of-this + * @tags testability + * maintainability + * language-features + */ + +import java + +from InstanceOfExpr ioe, RefType t, RefType ct +where + ioe.getExpr() instanceof ThisAccess and + t = ioe.getExpr().getType() and + ct = ioe.getTypeName().getType() and + ct.getASupertype*() = t +select ioe, "Testing whether 'this' is an instance of $@ in $@ introduces a dependency cycle between the two types.", + ct, ct.getName(), + t, t.getName() diff --git a/java/ql/src/Language Abuse/EmptyStatement.java b/java/ql/src/Language Abuse/EmptyStatement.java new file mode 100644 index 00000000000..4f9b462a38f --- /dev/null +++ b/java/ql/src/Language Abuse/EmptyStatement.java @@ -0,0 +1,8 @@ +public class Cart { + // AVOID: Empty statement + List items = new ArrayList();; + public void applyDiscount(float discount) { + // AVOID: Empty statement as loop body + for (int i = 0; i < items.size(); items.get(i++).applyDiscount(discount)); + } +} \ No newline at end of file diff --git a/java/ql/src/Language Abuse/EmptyStatement.qhelp b/java/ql/src/Language Abuse/EmptyStatement.qhelp new file mode 100644 index 00000000000..4a5a32f3fea --- /dev/null +++ b/java/ql/src/Language Abuse/EmptyStatement.qhelp @@ -0,0 +1,39 @@ + + + + + +

    An empty statement is a single semicolon ; that does not +terminate another statement. Such a statement hinders readability and has no effect on its own.

    + +
    + + +

    Avoid empty statements. If a loop is intended to have an empty body, it is better +to mark that fact explicitly by using a pair of braces {} containing an explanatory comment +for the body, rather than a single semicolon.

    + +
    + + +

    In the following example, there is an empty statement on line 3, where an additional semicolon is +used. On line 6, the for statement has an empty body because the condition is +immediately followed by a semicolon. In this case, it is better to include a pair of braces {} containing +an explanatory comment for the body instead. + +

    + +
    + + + +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • + + +
    +
    diff --git a/java/ql/src/Language Abuse/EmptyStatement.ql b/java/ql/src/Language Abuse/EmptyStatement.ql new file mode 100644 index 00000000000..0fcc7e7cadd --- /dev/null +++ b/java/ql/src/Language Abuse/EmptyStatement.ql @@ -0,0 +1,21 @@ +/** + * @name Empty statement + * @description An empty statement hinders readability. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/empty-statement + * @tags maintainability + * useless-code + */ + +import java + +from EmptyStmt empty, string action +where + if exists(LoopStmt l | l.getBody() = empty) then ( + action = "turned into '{}'" + ) else ( + action = "deleted" + ) +select empty, "This empty statement should be " + action + "." diff --git a/java/ql/src/Language Abuse/EnumIdentifier.java b/java/ql/src/Language Abuse/EnumIdentifier.java new file mode 100644 index 00000000000..eb4e45b2141 --- /dev/null +++ b/java/ql/src/Language Abuse/EnumIdentifier.java @@ -0,0 +1,7 @@ +class Old +{ + public static void main(String[] args) { + int enum = 13; // AVOID: 'enum' is a variable. + System.out.println("The value of enum is " + enum); + } +} diff --git a/java/ql/src/Language Abuse/EnumIdentifier.qhelp b/java/ql/src/Language Abuse/EnumIdentifier.qhelp new file mode 100644 index 00000000000..37eaa77c5ce --- /dev/null +++ b/java/ql/src/Language Abuse/EnumIdentifier.qhelp @@ -0,0 +1,40 @@ + + + + + +

    Enumerations, or enums, were introduced in Java 5, with the keyword +enum. Code written before this may use enum as +an identifier. To compile such code, you must compile it with a command +such as javac -source 1.4 .... However, this means that you +cannot use any new features that are provided in Java 5 and later.

    + +
    + + +

    To make it easier to compile the code and add code that uses new Java features, +rename any identifiers that are named enum in legacy code.

    + +
    + + +

    In the following example, enum is used as the name of a variable. This means that the +code does not compile unless the compiler's source language is set to 1.4 or earlier. To avoid this +constraint, the variable should be renamed.

    + + + +
    + + + +
  • +Java Language Specification: +8.9 Enums. +
  • + + +
    +
    diff --git a/java/ql/src/Language Abuse/EnumIdentifier.ql b/java/ql/src/Language Abuse/EnumIdentifier.ql new file mode 100644 index 00000000000..9dfc7b8a563 --- /dev/null +++ b/java/ql/src/Language Abuse/EnumIdentifier.ql @@ -0,0 +1,21 @@ +/** + * @name String 'enum' used as identifier + * @description Using 'enum' as an identifier makes the code incompatible with Java 5 and later. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/enum-identifier + * @tags portability + * readability + * naming + */ + +import java + +Element elementNamedEnum() { + result.(CompilationUnit).getPackage().getName().regexpMatch("(.*\\.|)enum(\\..*|)") + or + result.getName() = "enum" +} + +select elementNamedEnum(), "Code using 'enum' as an identifier will not compile with a recent version of Java." diff --git a/java/ql/src/Language Abuse/ImplementsAnnotation.java b/java/ql/src/Language Abuse/ImplementsAnnotation.java new file mode 100644 index 00000000000..5b03fefe7e1 --- /dev/null +++ b/java/ql/src/Language Abuse/ImplementsAnnotation.java @@ -0,0 +1,3 @@ +public abstract class ImplementsAnnotation implements Deprecated { + // ... +} \ No newline at end of file diff --git a/java/ql/src/Language Abuse/ImplementsAnnotation.qhelp b/java/ql/src/Language Abuse/ImplementsAnnotation.qhelp new file mode 100644 index 00000000000..a60fd9a67b6 --- /dev/null +++ b/java/ql/src/Language Abuse/ImplementsAnnotation.qhelp @@ -0,0 +1,52 @@ + + + + + +

    Although an annotation type is a special kind of interface that +can be implemented by a concrete class, this is not its intended use. +It is more likely that an annotation type should be used to annotate a class. +

    + +
    + + +

    Ensure that any annotations are used to annotate a class, unless they are really supposed to be +extended or implemented by the class. +

    + +
    + + +

    In the following example, the annotation Deprecated is implemented by the class +ImplementsAnnotation.

    + + + +

    The following example shows the intended use of annotations: to annotate the class +ImplementsAnnotationFix.

    + + + +
    + + + +
  • + The Java Language Specification: + Annotation Types. +
  • +
  • + The Java Tutorials: + Annotations. +
  • +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • + + +
    +
    diff --git a/java/ql/src/Language Abuse/ImplementsAnnotation.ql b/java/ql/src/Language Abuse/ImplementsAnnotation.ql new file mode 100644 index 00000000000..5732729bc6e --- /dev/null +++ b/java/ql/src/Language Abuse/ImplementsAnnotation.ql @@ -0,0 +1,17 @@ +/** + * @name Annotation is extended or implemented + * @description Extending or implementing an annotation is unlikely to be what the programmer intends. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/annotation-subtype + * @tags maintainability + * correctness + * logic + */ + +import java + +from RefType type, AnnotationType annotation +where type.getASupertype() = annotation +select type, "Should this class be annotated by '" + annotation.getName() + "', not have it as a super-type?" diff --git a/java/ql/src/Language Abuse/ImplementsAnnotationGood.java b/java/ql/src/Language Abuse/ImplementsAnnotationGood.java new file mode 100644 index 00000000000..d465283ec35 --- /dev/null +++ b/java/ql/src/Language Abuse/ImplementsAnnotationGood.java @@ -0,0 +1,4 @@ +@Deprecated +public abstract class ImplementsAnnotationFix { + // ... +} \ No newline at end of file diff --git a/java/ql/src/Language Abuse/IterableClass.qll b/java/ql/src/Language Abuse/IterableClass.qll new file mode 100644 index 00000000000..dc1221ff8ba --- /dev/null +++ b/java/ql/src/Language Abuse/IterableClass.qll @@ -0,0 +1,18 @@ +import java + +/** A class that implements `java.lang.Iterable`. */ +class Iterable extends Class { + Iterable() { + isSourceDeclaration() and + getASourceSupertype+().hasQualifiedName("java.lang", "Iterable") + } + + /** The return value of a one-statement `iterator()` method. */ + Expr simpleIterator() { + exists(Method m | + m.getDeclaringType().getSourceDeclaration() = this and + m.getName() = "iterator" and + m.getBody().(SingletonBlock).getStmt().(ReturnStmt).getResult() = result + ) + } +} diff --git a/java/ql/src/Language Abuse/IterableIterator.qhelp b/java/ql/src/Language Abuse/IterableIterator.qhelp new file mode 100644 index 00000000000..1958cc9ef93 --- /dev/null +++ b/java/ql/src/Language Abuse/IterableIterator.qhelp @@ -0,0 +1,68 @@ + + + + + + +

    +When working with custom implementations of Iterator<T> it +is easy to add implements Iterable<T> and a simple +return this; implementation of iterator() to support +the for-each syntax. This can, however, hide subtle bugs and is +therefore not recommended. It is better to separate the two and use a main +representation that only implements Iterable<T> without +containing any iteration state. This object can then return a short-lived +Iterator<T> each time it needs to be traversed. +

    +

    +If this refactoring is undesirable for some reason, then the +iterator() method should at the very least throw an exception if +called more than once. +

    + +
    + + +

    +The following example does not distinguish the iterable from its iterator, and +therefore causes the second loop to terminate immediately without any effect. +

    + + +

    +The best solution is a refactoring along the following lines where +Iterable classes are used to pass around references to data. This +allows the Iterator instances to be short-lived and avoids the +sharing of iteration state. +

    + + +

    +If a refactoring, as described above, is too cumbersome or is otherwise +undesirable, then a guard can be inserted, as shown below. Using a guard +ensures that multiple iteration fails early, making it easier to find any related bugs. +This solution is less ideal than the +refactoring above, but nevertheless an improvement over the original. +

    + + +
    + + + +
  • +The Java Language Specification: +The enhanced for statement. +
  • +
  • +The Java API Specification: +Interface Iterable<T>, +Interface Iterator<T>, +Interface DirectoryStream<T>. +
  • + +
    + +
    diff --git a/java/ql/src/Language Abuse/IterableIterator.ql b/java/ql/src/Language Abuse/IterableIterator.ql new file mode 100644 index 00000000000..32e81193c83 --- /dev/null +++ b/java/ql/src/Language Abuse/IterableIterator.ql @@ -0,0 +1,38 @@ +/** + * @name Iterator implementing Iterable + * @description An 'Iterator' that also implements 'Iterable' by returning itself as its 'Iterator' + * does not support multiple traversals. This can lead to unexpected behavior when + * it is viewed as an 'Iterable'. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id java/iterator-implements-iterable + * @tags correctness + * reliability + */ +import java +import IterableClass + +/** An `Iterable` that is also its own `Iterator`. */ +class IterableIterator extends Iterable { + IterableIterator() { + simpleIterator() instanceof ThisAccess + } +} + +/** An `IterableIterator` that never returns any elements. */ +class EmptyIterableIterator extends IterableIterator { + EmptyIterableIterator() { + exists(Method m | + m.getDeclaringType().getSourceDeclaration() = this and + m.getName() = "hasNext" and + m.getBody().(SingletonBlock).getStmt().(ReturnStmt).getResult().(BooleanLiteral).getBooleanValue() = false + ) + } +} + +from IterableIterator i +where + // Exclude the empty iterator as that is safe to reuse. + not i instanceof EmptyIterableIterator +select i, "This Iterable is its own Iterator, but does not guard against multiple iterations." diff --git a/java/ql/src/Language Abuse/IterableIteratorBad.java b/java/ql/src/Language Abuse/IterableIteratorBad.java new file mode 100644 index 00000000000..aa3dc5a4997 --- /dev/null +++ b/java/ql/src/Language Abuse/IterableIteratorBad.java @@ -0,0 +1,26 @@ +class ElemIterator implements Iterator, Iterable { + private MyElem[] data; + private idx = 0; + + public boolean hasNext() { + return idx < data.length; + } + public MyElem next() { + return data[idx++]; + } + public Iterator iterator() { + return this; + } + // ... +} + +void useMySequence(Iterable s) { + // do some work by traversing the sequence + for (MyElem e : s) { + // ... + } + // do some more work by traversing it again + for (MyElem e : s) { + // ... + } +} diff --git a/java/ql/src/Language Abuse/IterableIteratorGood1.java b/java/ql/src/Language Abuse/IterableIteratorGood1.java new file mode 100644 index 00000000000..28534f164a1 --- /dev/null +++ b/java/ql/src/Language Abuse/IterableIteratorGood1.java @@ -0,0 +1,16 @@ +class ElemSequence implements Iterable { + private MyElem[] data; + + public Iterator iterator() { + return new Iterator() { + private idx = 0; + public boolean hasNext() { + return idx < data.length; + } + public MyElem next() { + return data[idx++]; + } + }; + } + // ... +} diff --git a/java/ql/src/Language Abuse/IterableIteratorGood2.java b/java/ql/src/Language Abuse/IterableIteratorGood2.java new file mode 100644 index 00000000000..8b434dce5eb --- /dev/null +++ b/java/ql/src/Language Abuse/IterableIteratorGood2.java @@ -0,0 +1,19 @@ +class ElemIterator implements Iterator, Iterable { + private MyElem[] data; + private idx = 0; + private boolean usedAsIterable = false; + + public boolean hasNext() { + return idx < data.length; + } + public MyElem next() { + return data[idx++]; + } + public Iterator iterator() { + if (usedAsIterable || idx > 0) + throw new IllegalStateException(); + usedAsIterable = true; + return this; + } + // ... +} diff --git a/java/ql/src/Language Abuse/IterableOverview.qhelp b/java/ql/src/Language Abuse/IterableOverview.qhelp new file mode 100644 index 00000000000..9690d58f098 --- /dev/null +++ b/java/ql/src/Language Abuse/IterableOverview.qhelp @@ -0,0 +1,33 @@ + + + + +

    +Java has two interfaces for dealing with iteration, +Iterable<T> and Iterator<T>. An +Iterable<T> represents a sequence of elements that can be +traversed, and an Iterator<T> represents the +state of an ongoing traversal. As an example, all the +Collection<T> classes in the Java standard library implement +Iterable<T>. Comparing this to a traditional +for loop that increments an integer index and iterates over the +elements of an array, then the Iterable<T> object +corresponds to the array, whereas the Iterator<T> object +corresponds to the index variable. +

    +

    +Implementations of Iterable<T> are generally expected to +support multiple traversals of the element sequence they represent, although +there can be exceptions if the underlying data somehow makes this undesirable, +see for example DirectoryStream<T>. If an implementation of +Iterable<T> does not support multiple iterations, then its +iterator() method should throw an exception on its second and +subsequent calls. This makes bugs easier to find if such an +Iterable<T> is used more than once, for example in two +different for-each loops. +

    +
    + +
    diff --git a/java/ql/src/Language Abuse/MissedTernaryOpportunity.java b/java/ql/src/Language Abuse/MissedTernaryOpportunity.java new file mode 100644 index 00000000000..b9f087cebca --- /dev/null +++ b/java/ql/src/Language Abuse/MissedTernaryOpportunity.java @@ -0,0 +1,30 @@ +public class MissedTernaryOpportunity { + private static int myAbs1(int x) { + // Violation + if(x >= 0) + return x; + else + return -x; + } + + private static int myAbs2(int x) { + // Better + return x >= 0 ? x : -x; + } + + public static void main(String[] args) { + int i = 23; + + // Violation + String s1; + if(i == 23) + s1 = "Foo"; + else + s1 = "Bar"; + System.out.println(s1); + + // Better + String s2 = i == 23 ? "Foo" : "Bar"; + System.out.println(s2); + } +} \ No newline at end of file diff --git a/java/ql/src/Language Abuse/MissedTernaryOpportunity.qhelp b/java/ql/src/Language Abuse/MissedTernaryOpportunity.qhelp new file mode 100644 index 00000000000..7e9099a1d20 --- /dev/null +++ b/java/ql/src/Language Abuse/MissedTernaryOpportunity.qhelp @@ -0,0 +1,36 @@ + + + + +

    +An if statement where both branches do nothing but return or write to a variable can be +better expressed using the ternary ? operator. +

    + +

    +Use of the ternary operator enhances readability in two ways:

    +
      +
    • +It focuses the reader's attention on the intent of the code (to return or write) rather than the +testing of a condition.
    • +
    • +It is more concise, reducing the amount of code that needs to be read.
    • +
    • +You can initialize a variable conditionally on the line on which it is declared, rather than +assigning to it after initialization. This ensures that you initialize the variable as you intended.
    • +
    + +
    + +

    Consider using a ternary operator in this situation.

    + +
    + +

    The following code includes two examples of if statements, myAbs1 and +1, which can be simplified using the ternary operator. myAbs2 and s2 +show how the statements can be improved.

    + +
    +
    diff --git a/java/ql/src/Language Abuse/MissedTernaryOpportunity.ql b/java/ql/src/Language Abuse/MissedTernaryOpportunity.ql new file mode 100644 index 00000000000..4a41fdff917 --- /dev/null +++ b/java/ql/src/Language Abuse/MissedTernaryOpportunity.ql @@ -0,0 +1,74 @@ +/** + * @name Missed ternary opportunity + * @description An 'if' statement where both branches either + * (a) return or (b) write to the same variable + * can often be expressed more clearly using the '?' operator. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/missed-ternary-operator + * @tags maintainability + * language-features + */ + +import java + +predicate complicatedBranch(Stmt branch) { + exists(ConditionalExpr ce | + ce.getParent*() = branch + ) or + count(MethodAccess a | a.getParent*() = branch) > 1 +} + +predicate complicatedCondition(Expr cond) { + exists(Expr e | e = cond.getAChildExpr*() | + e instanceof AndLogicalExpr or + e instanceof OrLogicalExpr + ) +} + +predicate toCompare(Expr left, Expr right) { + exists(IfStmt is, AssignExpr at, AssignExpr ae | + at.getParent() = is.getThen() and + ae.getParent() = is.getElse() + | + left = at.getDest() and right = ae.getDest() or + left = at.getDest().(VarAccess).getQualifier() and right = ae.getDest().(VarAccess).getQualifier() + ) +} + +predicate sameVariable(VarAccess left, VarAccess right) { + toCompare(left, right) and + left.getVariable() = right.getVariable() and + ( + exists(Expr q1, Expr q2 | + left.getQualifier() = q1 and + sameVariable(q1, q2) and + right.getQualifier() = q2 + ) or + left.isLocal() and right.isLocal() + ) +} + +from IfStmt is, string what +where + ( + is.getThen() instanceof ReturnStmt and + is.getElse() instanceof ReturnStmt and + what = "return" + or + exists(AssignExpr at, AssignExpr ae | + at.getParent() = is.getThen() and + ae.getParent() = is.getElse() and + sameVariable(at.getDest(), ae.getDest()) and + what = "write to the same variable" + ) + ) and + // Exclusions. + not ( + exists(IfStmt other | is = other.getElse()) or + complicatedCondition(is.getCondition()) or + complicatedBranch(is.getThen()) or + complicatedBranch(is.getElse()) + ) +select is, "Both branches of this 'if' statement " + what + " - consider using '?' to express intent better." diff --git a/java/ql/src/Language Abuse/OverridePackagePrivate.java b/java/ql/src/Language Abuse/OverridePackagePrivate.java new file mode 100644 index 00000000000..bf6d68c67d7 --- /dev/null +++ b/java/ql/src/Language Abuse/OverridePackagePrivate.java @@ -0,0 +1,29 @@ +// File 1 +package gui; + +abstract class Widget +{ + // ... + + // Return the width (in pixels) of this widget + int width() { + // ... + } + + // ... +} + +// File 2 +package gui.extras; + +class PhotoResizerWidget extends Widget +{ + // ... + + // Return the new width (of the photo when resized) + public int width() { + // ... + } + + // ... +} \ No newline at end of file diff --git a/java/ql/src/Language Abuse/OverridePackagePrivate.qhelp b/java/ql/src/Language Abuse/OverridePackagePrivate.qhelp new file mode 100644 index 00000000000..c1b5a774d87 --- /dev/null +++ b/java/ql/src/Language Abuse/OverridePackagePrivate.qhelp @@ -0,0 +1,64 @@ + + + + + +

    +If a method is declared with default access (that is, not +private, protected, nor public), it can +only be overridden by methods in the same package. If a method of the same +signature is defined in a subclass in a different package, it is a +completely separate method and no overriding occurs. +

    + +

    +Code like this can be confusing for other programmers, who have to +understand that there is no overriding relation, check that the +original programmer did not intend one method to override the other, and +avoid mixing up the two methods by accident. +

    + +
    + + +

    In cases where there is intentionally no overriding, the best solution is to rename one or both +of the methods to clarify their different purposes.

    + +

    If one method is supposed to override another method that is declared with default access in +another package, the access of the method must be changed to +public or protected. Alternatively, the classes must be moved to +the same package.

    + +
    + + +

    +In the following example, PhotoResizerWidget.width does not override +Widget.width because one method is in package gui and one method is in +package gui.extras. +

    + + + +

    Assuming that no overriding is intentional, one or both of the methods should be renamed. For +example, PhotoResizerWidget.width would be better named +PhotoResizerWidget.newPhotoWidth.

    + +
    + + + +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • +
  • +Java Language Specification: +8.4.8.1 Overriding (by Instance Methods). +
  • + + +
    +
    diff --git a/java/ql/src/Language Abuse/OverridePackagePrivate.ql b/java/ql/src/Language Abuse/OverridePackagePrivate.ql new file mode 100644 index 00000000000..8b6ec0e805a --- /dev/null +++ b/java/ql/src/Language Abuse/OverridePackagePrivate.ql @@ -0,0 +1,24 @@ +/** + * @name Confusing non-overriding of package-private method + * @description A method that appears to override another method but does not, because the + * declaring classes are in different packages, is potentially confusing. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/non-overriding-package-private + * @tags maintainability + * readability + */ + +import java + +from Method superMethod, Method method +where + overridesIgnoringAccess(method, _, superMethod, _) and + not method.overrides(superMethod) and + not superMethod.isPublic() and + not superMethod.isProtected() and + not superMethod.isPrivate() +select method, "This method does not override $@ because it is private to another package.", + superMethod, + superMethod.getDeclaringType().getName() + "." + superMethod.getName() diff --git a/java/ql/src/Language Abuse/TypeVarExtendsFinalType.java b/java/ql/src/Language Abuse/TypeVarExtendsFinalType.java new file mode 100644 index 00000000000..fc5f09bb7f4 --- /dev/null +++ b/java/ql/src/Language Abuse/TypeVarExtendsFinalType.java @@ -0,0 +1,7 @@ +class Printer +{ + void print(List strings) { // Unnecessary wildcard + for (String s : strings) + System.out.println(s); + } +} \ No newline at end of file diff --git a/java/ql/src/Language Abuse/TypeVarExtendsFinalType.qhelp b/java/ql/src/Language Abuse/TypeVarExtendsFinalType.qhelp new file mode 100644 index 00000000000..810f46280ea --- /dev/null +++ b/java/ql/src/Language Abuse/TypeVarExtendsFinalType.qhelp @@ -0,0 +1,48 @@ + + + + + +

    A type wildcard with an extends clause (for example +? extends String) implicitly suggests that a type (in +this case String) has subclasses. If the type in the extends +clause is final, the code is confusing because a final class cannot have +any subclasses. The only type that satisfies ? extends String is +String.

    + +
    + + +

    To make the code more readable, omit the wildcard to leave just the final type.

    + +
    + + +

    In the following example, a wildcard is used to refer to any type that is a subclass of +String.

    + + + +

    However, because String is declared final, it does not have any +subclasses. Therefore, it is clearer to replace ? extends String with +String.

    + +
    + + + +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • +
  • +Java Language Specification: +4.5.1 Type Arguments and Wildcards, +8.1.1.2 final Classes. +
  • + + +
    +
    diff --git a/java/ql/src/Language Abuse/TypeVarExtendsFinalType.ql b/java/ql/src/Language Abuse/TypeVarExtendsFinalType.ql new file mode 100644 index 00000000000..c9247096f1a --- /dev/null +++ b/java/ql/src/Language Abuse/TypeVarExtendsFinalType.ql @@ -0,0 +1,21 @@ +/** + * @name Type bound extends a final class + * @description If 'C' is a final class, a type bound such as '? extends C' + * is confusing because it implies that 'C' has subclasses, but + * a final class has no subclasses. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/type-bound-extends-final + * @tags maintainability + * readability + * types + */ + +import java + +from TypeVariable v, RefType bound +where + v.getATypeBound().getType() = bound and + bound.isFinal() +select v, "Type '" + bound + "' is final, so <" + v.getName() + " extends " + bound + "> is confusing." diff --git a/java/ql/src/Language Abuse/TypeVariableHidesType.java b/java/ql/src/Language Abuse/TypeVariableHidesType.java new file mode 100644 index 00000000000..758226265be --- /dev/null +++ b/java/ql/src/Language Abuse/TypeVariableHidesType.java @@ -0,0 +1,7 @@ +import java.util.Map; +import java.util.Map.Entry; + +class Mapping // The type variable 'Entry' shadows the imported interface 'Entry'. +{ + // ... +} \ No newline at end of file diff --git a/java/ql/src/Language Abuse/TypeVariableHidesType.qhelp b/java/ql/src/Language Abuse/TypeVariableHidesType.qhelp new file mode 100644 index 00000000000..ff135efda01 --- /dev/null +++ b/java/ql/src/Language Abuse/TypeVariableHidesType.qhelp @@ -0,0 +1,48 @@ + + + + + +

    Type shadowing occurs if two types have the same name but one is defined within +the scope of the other. This can arise if you introduce a type variable +with the same name as an imported class.

    + +

    Type shadowing may cause the two types to be confused, which can lead to various problems.

    + +
    + + +

    Name the type variable so that its name does not clash with the +imported class.

    + +
    + + +

    In the following example, the type java.util.Map.Entry is imported at the top of the +file, but the class Mapping is defined with two type variables, +Key and Entry. Uses of Entry within the +Mapping class refer to the type variable, and not the imported +interface. The type variable therefore shadows Map.Entry.

    + + + +

    To fix the code, the type variable Entry on line 4 should be renamed.

    + +
    + + + +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • +
  • +Java Language Specification: +6.4 Shadowing and Obscuring. +
  • + + +
    +
    diff --git a/java/ql/src/Language Abuse/TypeVariableHidesType.ql b/java/ql/src/Language Abuse/TypeVariableHidesType.ql new file mode 100644 index 00000000000..0fd4dfaf045 --- /dev/null +++ b/java/ql/src/Language Abuse/TypeVariableHidesType.ql @@ -0,0 +1,39 @@ +/** + * @name Type variable hides another type + * @description A type variable with the same name as another type that is in scope can cause + * the two types to be confused. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/type-variable-hides-type + * @tags reliability + * readability + * types + */ + +import java + +RefType anOuterType(TypeVariable var) { + var.getGenericCallable().getDeclaringType() = result + or + var.getGenericType() = result + or + result = anOuterType(var).(NestedType).getEnclosingType() +} + +RefType aTypeVisibleFrom(TypeVariable var) { + result = anOuterType(var) + or + exists(ImportType i | + var.getLocation().getFile() = i.getCompilationUnit() and + result = i.getImportedType() + ) + or + (var.getPackage() = result.getPackage() and result instanceof TopLevelType) +} + +from RefType hidden, TypeVariable var +where + hidden = aTypeVisibleFrom(var) and + var.getName() = hidden.getName() +select var, "Type $@ is hidden by this type variable.", hidden, hidden.getQualifiedName() diff --git a/java/ql/src/Language Abuse/UselessNullCheck.qhelp b/java/ql/src/Language Abuse/UselessNullCheck.qhelp new file mode 100644 index 00000000000..1f21ee33b8e --- /dev/null +++ b/java/ql/src/Language Abuse/UselessNullCheck.qhelp @@ -0,0 +1,42 @@ + + + + +

    +Sometimes you can guarantee that a particular variable will never be null. +For example when that variable has just been assigned a newly created +object or is the exception caught by a catch clause. +A null check on such a variable is misleading, and can potentially indicate a +logic error. +

    +
    + + +

    +Do not check a variable for null if a null value is clearly impossible. +

    + +
    + + +

    +The following example shows a null check on a newly created object. An object +returned by new can never be null, so this check is superfluous. +

    + + +
    + + + +
  • +Java Language Specification: +Creation of New Class Instances, +Execution of try-catch. +
  • + +
    + +
    diff --git a/java/ql/src/Language Abuse/UselessNullCheck.ql b/java/ql/src/Language Abuse/UselessNullCheck.ql new file mode 100644 index 00000000000..16f024c9137 --- /dev/null +++ b/java/ql/src/Language Abuse/UselessNullCheck.ql @@ -0,0 +1,28 @@ +/** + * @name Useless null check + * @description Checking whether an expression is null when that expression cannot + * possibly be null is useless. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id java/useless-null-check + * @tags maintainability + * useless-code + * external/cwe/cwe-561 + */ + +import java +import semmle.code.java.dataflow.NullGuards +import semmle.code.java.controlflow.Guards + +from Expr guard, Expr e, Expr reason, string msg +where + guard = basicNullGuard(e, _, true) and + e = clearlyNotNullExpr(reason) and + (if reason instanceof Guard then + msg = "This check is useless, $@ cannot be null here, since it is guarded by $@." + else if reason != e then + msg = "This check is useless, $@ cannot be null here, since $@ always is non-null." + else + msg = "This check is useless, since $@ always is non-null.") +select guard, msg, e, e.toString(), reason, reason.toString() diff --git a/java/ql/src/Language Abuse/UselessNullCheckBad.java b/java/ql/src/Language Abuse/UselessNullCheckBad.java new file mode 100644 index 00000000000..4f8ffdf8f5f --- /dev/null +++ b/java/ql/src/Language Abuse/UselessNullCheckBad.java @@ -0,0 +1,4 @@ +Object o = new Object(); +if (o == null) { + // this cannot happen! +} diff --git a/java/ql/src/Language Abuse/UselessTypeTest.java b/java/ql/src/Language Abuse/UselessTypeTest.java new file mode 100644 index 00000000000..3ab8dfb3a09 --- /dev/null +++ b/java/ql/src/Language Abuse/UselessTypeTest.java @@ -0,0 +1,11 @@ +public class UselessTypeTest { + private static class B {} + private static class D extends B {} + + public static void main(String[] args) { + D d = new D(); + if(d instanceof B) { // violation + System.out.println("Mon dieu, d is a B!"); + } + } +} \ No newline at end of file diff --git a/java/ql/src/Language Abuse/UselessTypeTest.qhelp b/java/ql/src/Language Abuse/UselessTypeTest.qhelp new file mode 100644 index 00000000000..e767309c6a7 --- /dev/null +++ b/java/ql/src/Language Abuse/UselessTypeTest.qhelp @@ -0,0 +1,24 @@ + + + +

    +It is always the case that, for any type B, an instance of a type derived from B is also an instance of B. +There is no need to explicitly test that this relationship exists. +

    + +
    + + +

    +Remove the unnecessary type test to simplify the code. +

    + +
    + +

    The following example shows an unnecessary type test.

    + + +
    +
    diff --git a/java/ql/src/Language Abuse/UselessTypeTest.ql b/java/ql/src/Language Abuse/UselessTypeTest.ql new file mode 100644 index 00000000000..25085212544 --- /dev/null +++ b/java/ql/src/Language Abuse/UselessTypeTest.ql @@ -0,0 +1,22 @@ +/** + * @name Useless type test + * @description Testing whether a derived type is an instance of its base type is unnecessary. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id java/useless-type-test + * @tags maintainability + * language-features + * external/cwe/cwe-561 + */ + +import java + +from InstanceOfExpr ioe, RefType t, RefType ct +where + t = ioe.getExpr().getType() and + ct = ioe.getTypeName().getType() and + ct = t.getASupertype+() +select ioe, "There is no need to test whether an instance of $@ is also an instance of $@ - it always is.", + t, t.getName(), + ct, ct.getName() diff --git a/java/ql/src/Language Abuse/UselessUpcast.java b/java/ql/src/Language Abuse/UselessUpcast.java new file mode 100644 index 00000000000..b1db05a4b91 --- /dev/null +++ b/java/ql/src/Language Abuse/UselessUpcast.java @@ -0,0 +1,25 @@ +public class UselessUpcast { + private static class B {} + private static class D extends B {} + + private static void Foo(B b) { System.out.println("Foo(B)"); } + private static void Foo(D d) { System.out.println("Foo(D)"); } + + private static class Expr {} + private static class AddExpr extends Expr {} + private static class SubExpr extends Expr {} + + public static void main(String[] args) { + D d = new D(); + B b_ = (B)d; // violation: redundant cast, consider removing + + B b = new D(); + D d_ = (D)b; // non-violation: required downcast + + Foo(d); + Foo((B)d); // non-violation: required to call Foo(B) + + // Non-violation: required to specify the type of the ternary operands. + Expr e = d != null ? (Expr)new AddExpr() : new SubExpr(); + } +} \ No newline at end of file diff --git a/java/ql/src/Language Abuse/UselessUpcast.qhelp b/java/ql/src/Language Abuse/UselessUpcast.qhelp new file mode 100644 index 00000000000..4bc648f45ca --- /dev/null +++ b/java/ql/src/Language Abuse/UselessUpcast.qhelp @@ -0,0 +1,36 @@ + + + + +

    +In most situations, casting an instance of a derived type to a base type serves no purpose, since the conversion can be done implicitly. In such cases, +the redundant cast can simply be removed. However, an upcast is not redundant in the following situations: +

    + +
      +
    • It is being used to force a call to an overloaded callable that takes a parameter of the base type rather than one of the derived type.
    • +
    • It is being used to specify the type to use for the operands of a ternary expression.
    • +
    + +

    +Both of these special cases are illustrated in the example below. This rule ignores these +special cases and highlights upcasts which appear to be redundant. +

    + +
    + + +

    +Remove the unnecessary upcast to simplify the code. +

    + +
    + +

    The following code includes an example of a redundant upcast that would be highlighted by this rule. +In addition, three examples of upcasts that are required and are ignored by this rule.

    + + +
    +
    diff --git a/java/ql/src/Language Abuse/UselessUpcast.ql b/java/ql/src/Language Abuse/UselessUpcast.ql new file mode 100644 index 00000000000..fe8b6fe6a0c --- /dev/null +++ b/java/ql/src/Language Abuse/UselessUpcast.ql @@ -0,0 +1,68 @@ +/** + * @name Useless upcast + * @description Upcasting a derived type to its base type is usually unnecessary. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/useless-upcast + * @tags maintainability + * language-features + * external/cwe/cwe-561 + */ + +import java + +predicate usefulUpcast(CastExpr e) { + // Upcasts that may be performed to affect resolution of methods or constructors. + exists(Call c, int i, Callable target | + c.getArgument(i).getProperExpr() = e and + target = c.getCallee() and + // An upcast to the type of the corresponding parameter. + e.getType() = target.getParameterType(i) + | + // There is an overloaded method/constructor in the class that we might be trying to avoid. + exists(Callable other | + other.getName() = target.getName() and + other.getSourceDeclaration() != target.getSourceDeclaration() + | + c.(MethodAccess).getReceiverType().(RefType).inherits((Method)other) or + other = target.(Constructor).getDeclaringType().getAConstructor() + ) + ) + or + // Upcasts of a varargs argument. + exists(Call c, int iArg, int iParam | c.getArgument(iArg).getProperExpr() = e | + c.getCallee().getParameter(iParam).isVarargs() and iArg >= iParam + ) + or + // Upcasts that are performed on an operand of a ternary expression. + exists(ConditionalExpr ce | e = ce.getTrueExpr().getProperExpr() or e = ce.getFalseExpr().getProperExpr()) + or + // Upcasts to raw types. + e.getType() instanceof RawType + or + e.getType().(Array).getElementType() instanceof RawType + or + // Upcasts that are performed to affect field, private method, or static method resolution. + exists(FieldAccess fa | e = fa.getQualifier().getProperExpr() | + not e.getExpr().getType().(RefType).inherits(fa.getField()) + ) or + exists(MethodAccess ma, Method m | + e = ma.getQualifier().getProperExpr() and m = ma.getMethod() and (m.isStatic() or m.isPrivate()) + | + not e.getExpr().getType().(RefType).inherits(m) + ) +} + +from Expr e, RefType src, RefType dest +where + exists(CastExpr cse | cse = e | + exists(cse.getLocation()) and + src = cse.getExpr().getType() and + dest = cse.getType() + ) and + dest = src.getASupertype+() and + not usefulUpcast(e) +select e, "There is no need to upcast from $@ to $@ - the conversion can be done implicitly.", + src, src.getName(), + dest, dest.getName() diff --git a/java/ql/src/Language Abuse/WrappedIterator.qhelp b/java/ql/src/Language Abuse/WrappedIterator.qhelp new file mode 100644 index 00000000000..9c8b0817c55 --- /dev/null +++ b/java/ql/src/Language Abuse/WrappedIterator.qhelp @@ -0,0 +1,60 @@ + + + + + + +

    +When writing the iterator() method in an +Iterable<T> then it is important to make sure that each call +will result in a fresh Iterator<T> instance containing all +the necessary state for keeping track of the iteration. If the iterator is +stored in the Iterable<T>, or somehow refers to iteration +state stored in the Iterable<T>, then subsequent calls to +iterator() can result in loops that only traverse a subset of the +elements or have no effect at all. +

    + +
    + + +

    +The following example returns the same iterator on every call, and therefore +causes the second loop to terminate immediately without any effect. +

    + + +

    +This second example returns a newly created iterator each time, +but still relies on iteration state stored +in the surrounding class, and therefore also causes the second loop to +terminate immediately. +

    + + +

    +The code should instead be written like this, such that each call to +iterator() correctly gives a fresh iterator that starts at the +beginning. +

    + +
    + + + +
  • +The Java Language Specification: +The enhanced for statement. +
  • +
  • +The Java API Specification: +Interface Iterable<T>, +Interface Iterator<T>, +Interface DirectoryStream<T>. +
  • + +
    + +
    diff --git a/java/ql/src/Language Abuse/WrappedIterator.ql b/java/ql/src/Language Abuse/WrappedIterator.ql new file mode 100644 index 00000000000..2376d87c4e7 --- /dev/null +++ b/java/ql/src/Language Abuse/WrappedIterator.ql @@ -0,0 +1,49 @@ +/** + * @name Iterable wrapping an iterator + * @description An 'Iterable' that reuses an 'Iterator' instance does not support multiple traversals + * and can lead to unexpected behavior. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id java/iterable-wraps-iterator + * @tags correctness + * reliability + */ +import java +import IterableClass + +/** An `Iterable` that is merely a thin wrapper around a contained `Iterator`. */ +predicate iteratorWrapper(Iterable it, Field f, boolean wrap) { + // A class that implements `java.lang.Iterable` and + // declares a final or effectively final field ... + f.getDeclaringType().getSourceDeclaration() = it and + ( + f.isFinal() or + ( + strictcount(f.getAnAssignedValue()) = 1 and + f.getAnAssignedValue().getEnclosingCallable() instanceof InitializerMethod + ) + ) and + // ... whose type is a sub-type of `java.util.Iterator` and ... + f.getType().(RefType).getASupertype*().getSourceDeclaration().hasQualifiedName("java.util", "Iterator") and + // ... whose value is returned by the `iterator()` method of this class ... + exists(Expr iterator | iterator = it.simpleIterator() | + // ... either directly ... + iterator = f.getAnAccess() and wrap = false or + // ... or wrapped in another Iterator. + exists(ClassInstanceExpr cie | cie = iterator and wrap = true | + cie.getAnArgument() = f.getAnAccess() or + cie.getAnonymousClass().getAMethod() = f.getAnAccess().getEnclosingCallable() + ) + ) +} + +from Iterable i, Field f, boolean wrap, string appearto, string iteratorbasedon +where + iteratorWrapper(i, f, wrap) and + ( wrap = true and appearto = "appear to " and iteratorbasedon = "an iterator based on " or + wrap = false and appearto = "" and iteratorbasedon = "" + ) +select i, "This class implements Iterable, but does not " + appearto + "support multiple iterations," + + " since its iterator method always returns " + iteratorbasedon + "the same $@.", + f, "iterator" diff --git a/java/ql/src/Language Abuse/WrappedIteratorBad1.java b/java/ql/src/Language Abuse/WrappedIteratorBad1.java new file mode 100644 index 00000000000..7a30d9257c6 --- /dev/null +++ b/java/ql/src/Language Abuse/WrappedIteratorBad1.java @@ -0,0 +1,19 @@ +class MySequence implements Iterable { + // ... some reference to data + final Iterator it = data.iterator(); + // Wrong: reused iterator + public Iterator iterator() { + return it; + } +} + +void useMySequence(MySequence s) { + // do some work by traversing the sequence + for (MyElem e : s) { + // ... + } + // do some more work by traversing it again + for (MyElem e : s) { + // ... + } +} diff --git a/java/ql/src/Language Abuse/WrappedIteratorBad2.java b/java/ql/src/Language Abuse/WrappedIteratorBad2.java new file mode 100644 index 00000000000..092e7ad79a3 --- /dev/null +++ b/java/ql/src/Language Abuse/WrappedIteratorBad2.java @@ -0,0 +1,18 @@ +class MySequence implements Iterable { + // ... some reference to data + final Iterator it = data.iterator(); + // Wrong: iteration state outside returned iterator + public Iterator iterator() { + return new Iterator() { + public boolean hasNext() { + return it.hasNext(); + } + public MyElem next() { + return transformElem(it.next()); + } + public void remove() { + // ... + } + }; + } +} diff --git a/java/ql/src/Language Abuse/WrappedIteratorGood.java b/java/ql/src/Language Abuse/WrappedIteratorGood.java new file mode 100644 index 00000000000..322c48530cd --- /dev/null +++ b/java/ql/src/Language Abuse/WrappedIteratorGood.java @@ -0,0 +1,18 @@ +class MySequence implements Iterable { + // ... some reference to data + public Iterator iterator() { + return new Iterator() { + // Correct: iteration state inside returned iterator + final Iterator it = data.iterator(); + public boolean hasNext() { + return it.hasNext(); + } + public MyElem next() { + return transformElem(it.next()); + } + public void remove() { + // ... + } + }; + } +} diff --git a/java/ql/src/Likely Bugs/Arithmetic/BadAbsOfRandom.java b/java/ql/src/Likely Bugs/Arithmetic/BadAbsOfRandom.java new file mode 100644 index 00000000000..96ab5134a36 --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/BadAbsOfRandom.java @@ -0,0 +1,17 @@ +public static void main(String args[]) { + Random r = new Random(); + + // BAD: 'mayBeNegativeInt' is negative if + // 'nextInt()' returns 'Integer.MIN_VALUE'. + int mayBeNegativeInt = Math.abs(r.nextInt()); + + // GOOD: 'nonNegativeInt' is always a value between 0 (inclusive) + // and Integer.MAX_VALUE (exclusive). + int nonNegativeInt = r.nextInt(Integer.MAX_VALUE); + + // GOOD: When 'nextInt' returns a negative number increment the returned value. + int nextInt = r.nextInt(); + if(nextInt < 0) + nextInt++; + int nonNegativeInt = Math.abs(nextInt); +} diff --git a/java/ql/src/Likely Bugs/Arithmetic/BadAbsOfRandom.qhelp b/java/ql/src/Likely Bugs/Arithmetic/BadAbsOfRandom.qhelp new file mode 100644 index 00000000000..27a1ac60834 --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/BadAbsOfRandom.qhelp @@ -0,0 +1,51 @@ + + + + + +

    +Using Math.abs on the result of a call to Random.nextInt() (or Random.nextLong()) is not guaranteed to +return a non-negative number. Random.nextInt() can return Integer.MIN_VALUE, which when passed to Math.abs +results in the same value, Integer.MIN_VALUE. (Because of the two's-complement representation of integers in Java, the positive equivalent of Integer.MIN_VALUE +cannot be represented in the same number of bits.) The case for Random.nextLong() is similar. +

    + +
    + +

    +If a non-negative random integer is required, use Random.nextInt(int) instead, and use Integer.MAX_VALUE as its parameter. +The values that might be returned do not include Integer.MAX_VALUE itself, but this solution is likely to be sufficient for most purposes. +

    + +

    Another solution is to increment the value of Random.nextInt() by one, if it is negative, before passing the result to +Math.abs. This solution has the advantage that 0 has the same probability as other numbers.

    + +
    + + +

    In the following example, mayBeNegativeInt +is negative if nextInt returns Integer.MIN_VALUE. +The example shows how using the two solutions described above means that positiveInt is always assigned a positive number.

    + + + +
    + + + +
  • +Java API Documentation: +Math.abs(int), +Math.abs(long), +Random. +
  • +
  • +Java Language Specification, 3rd ed: +4.2.1 Integral Types and Values. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Arithmetic/BadAbsOfRandom.ql b/java/ql/src/Likely Bugs/Arithmetic/BadAbsOfRandom.ql new file mode 100644 index 00000000000..27dc29d21d9 --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/BadAbsOfRandom.ql @@ -0,0 +1,24 @@ +/** + * @name Incorrect absolute value of random number + * @description Calling 'Math.abs' to find the absolute value of a randomly generated integer is not + * guaranteed to return a non-negative integer. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/abs-of-random + * @tags reliability + * maintainability + */ +import java + +from MethodAccess ma, Method abs, Method nextIntOrLong, MethodAccess nma +where + ma.getMethod() = abs and + abs.hasName("abs") and + abs.getDeclaringType().hasQualifiedName("java.lang","Math") and + ma.getAnArgument() = nma and + nma.getMethod() = nextIntOrLong and + (nextIntOrLong.hasName("nextInt") or nextIntOrLong.hasName("nextLong")) and + nextIntOrLong.getDeclaringType().hasQualifiedName("java.util","Random") and + nextIntOrLong.hasNoParameters() +select ma, "Incorrect computation of abs of signed integral random value." diff --git a/java/ql/src/Likely Bugs/Arithmetic/BadCheckOdd.java b/java/ql/src/Likely Bugs/Arithmetic/BadCheckOdd.java new file mode 100644 index 00000000000..06d28d549d3 --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/BadCheckOdd.java @@ -0,0 +1,9 @@ +class CheckOdd { + private static boolean isOdd(int x) { + return x % 2 == 1; + } + + public static void main(String[] args) { + System.out.println(isOdd(-9)); // prints false + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Arithmetic/BadCheckOdd.qhelp b/java/ql/src/Likely Bugs/Arithmetic/BadCheckOdd.qhelp new file mode 100644 index 00000000000..eb839ea1aed --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/BadCheckOdd.qhelp @@ -0,0 +1,45 @@ + + + + + +

    Avoid using x % 2 == 1 or x % 2 > 0 +to check whether a number x is odd, or +x % 2 != 1 to check whether it is even. +Such code does not work for negative numbers. +For example, -5 % 2 equals -1, not 1. +

    + +
    + + +

    +Consider using x % 2 != 0 to check for odd and x % 2 == 0 to check for even. +

    + +
    + +

    -9 is an odd number but this example does not detect it as one. This is because -9 % 2 + is -1, not 1.

    + + +

    It would be better to check if the number is even and then invert that check.

    + + +
    + + + +
  • + J. Bloch and N. Gafter, Java Puzzlers: Traps, Pitfalls, and Corner Cases, Puzzle 1. Addison-Wesley, 2005. +
  • +
  • + The Java Language Specification: + Remainder Operator %. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Arithmetic/BadCheckOdd.ql b/java/ql/src/Likely Bugs/Arithmetic/BadCheckOdd.ql new file mode 100644 index 00000000000..cde280a2e87 --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/BadCheckOdd.ql @@ -0,0 +1,35 @@ +/** + * @name Bad parity check + * @description Code that uses 'x % 2 == 1' or 'x % 2 > 0' to check whether a number is odd does not + * work for negative numbers. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/incomplete-parity-check + * @tags reliability + * correctness + * types + */ +import java +import semmle.code.java.Collections + +predicate isDefinitelyPositive(Expr e) { + isDefinitelyPositive(e.getProperExpr()) or + e.(IntegerLiteral).getIntValue() >= 0 or + e.(MethodAccess).getMethod() instanceof CollectionSizeMethod or + e.(MethodAccess).getMethod() instanceof StringLengthMethod or + e.(FieldAccess).getField() instanceof ArrayLengthField +} + +from BinaryExpr t, RemExpr lhs, IntegerLiteral rhs, string parity +where + t.getLeftOperand().getProperExpr() = lhs and + t.getRightOperand().getProperExpr() = rhs and + not isDefinitelyPositive(lhs.getLeftOperand()) and + lhs.getRightOperand().getProperExpr().(IntegerLiteral).getIntValue() = 2 and + ( + t instanceof EQExpr and rhs.getIntValue() = 1 and parity = "oddness" or + t instanceof NEExpr and rhs.getIntValue() = 1 and parity = "evenness" or + t instanceof GTExpr and rhs.getIntValue() = 0 and parity = "oddness" + ) +select t, "Possibly invalid test for " + parity + ". This will fail for negative numbers." diff --git a/java/ql/src/Likely Bugs/Arithmetic/BadCheckOddGood.java b/java/ql/src/Likely Bugs/Arithmetic/BadCheckOddGood.java new file mode 100644 index 00000000000..1e4a17d3b60 --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/BadCheckOddGood.java @@ -0,0 +1,9 @@ +class CheckOdd { + private static boolean isOdd(int x) { + return x % 2 != 0; + } + + public static void main(String[] args) { + System.out.println(isOdd(-9)); // prints true + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Arithmetic/CondExprTypes.java b/java/ql/src/Likely Bugs/Arithmetic/CondExprTypes.java new file mode 100644 index 00000000000..e278cf30824 --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/CondExprTypes.java @@ -0,0 +1,3 @@ +int i = 0; +System.out.print(true ? 'x' : 0); // prints "x" +System.out.print(true ? 'x' : i); // prints "120" \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Arithmetic/CondExprTypes.qhelp b/java/ql/src/Likely Bugs/Arithmetic/CondExprTypes.qhelp new file mode 100644 index 00000000000..d19bc5fb32f --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/CondExprTypes.qhelp @@ -0,0 +1,49 @@ + + + + + +

    Conditional expressions of the form (p ? e1 : e2) can +yield unexpected results if e1 and e2 have +distinct primitive types.

    + +
    + + +

    The following example illustrates the most confusing case, which occurs when one branch has type char +and the other branch does not have type char.

    + + + +

    This unexpected result is due to binary numeric promotion +of 'x' from char to int. +For details on the result type of +the conditional operator, see the references. +

    + +
    + + +

    When using the ternary conditional operator with numeric operands, +the second and third operand should have the same numeric type. +This avoids potentially unexpected results caused by binary numeric promotion. +

    + + +
    + + + +
  • + J. Bloch and N. Gafter, Java Puzzlers: Traps, Pitfalls, and Corner Cases, Puzzle 8. Addison-Wesley, 2005. +
  • +
  • + The Java Language Specification: + Conditional Operator ?. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Arithmetic/CondExprTypes.ql b/java/ql/src/Likely Bugs/Arithmetic/CondExprTypes.ql new file mode 100644 index 00000000000..78101eb303a --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/CondExprTypes.ql @@ -0,0 +1,35 @@ +/** + * @name Type mismatch in conditional expression + * @description Using the '(p?e1:e2)' operator with different primitive types for the second and + * third operands may cause unexpected results. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/type-mismatch-in-conditional + * @tags reliability + * correctness + */ +import java + +class CharType extends PrimitiveType { + CharType() { + this.hasName("char") + } +} + +private +Type getABranchType(ConditionalExpr ce) { + result = ce.getTrueExpr().getType() or + result = ce.getFalseExpr().getType() +} + +from ConditionalExpr ce +where + getABranchType(ce) instanceof CharType and + exists(Type t | t = getABranchType(ce) | + t instanceof PrimitiveType and + not t instanceof CharType + ) +select ce, "Mismatch between types of branches: $@ and $@.", + ce.getTrueExpr(), ce.getTrueExpr().getType().getName(), + ce.getFalseExpr(), ce.getFalseExpr().getType().getName() diff --git a/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.java b/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.java new file mode 100644 index 00000000000..7c4d0940a06 --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.java @@ -0,0 +1,3 @@ +public boolean isEven(int x) { + return x % 1 == 0; //Does not work +} diff --git a/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.qhelp b/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.qhelp new file mode 100644 index 00000000000..988a5c231ed --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.qhelp @@ -0,0 +1,66 @@ + + + + + +

    +Some expressions always evaluate to the same result, no matter what their subexpressions are: +

    + +
      +
    • x * 0 always evaluates to 0. +
    • +
    • x % 1 always evaluates to 0. +
    • +
    • x << 64 (when x is of type long) always evaluates to 0. +
    • +
    • x << 32 (when x is not of type long) always evaluates to 0. +
    • +
    • x & 0 always evaluates to 0. +
    • +
    • x || true always evaluates to true. +
    • +
    • x && false always evaluates to false. +
    • +
    + +

    +Whenever x is not constant, such an expression is often a mistake. +

    + +
    + + +

    +If the expression is supposed to evaluate to the same result every time it is executed, consider replacing the entire expression with its result. +

    + +
    + +

    +The following method tries to determine whether x is even by checking whether x % 1 == 0. +

    + + + +

    +However, x % 1 == 0 is always true when x is +an integer. The correct check is x % 2 == 0. +

    + + + +
    + + + +
  • + The Java Language Specification: + Multiplication operator *, Remainder operator %, Left shift operator <<, Bitwise AND operator &, Conditional-and operator && and Conditional-or operator ||. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.ql b/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.ql new file mode 100644 index 00000000000..a5991c360ab --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.ql @@ -0,0 +1,84 @@ +/** + * @name Expression always evaluates to the same value + * @description An expression that always evaluates to the same value, but which has a non-constant subexpression, indicates a mistake. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id java/evaluation-to-constant + * @tags maintainability + * useless-code + */ +import java + +int integralTypeWidth (IntegralType t) { + if (t.hasName("long") or t.hasName("Long")) + then result = 64 + else result = 32 +} + +int eval (Expr e) { + result = e.(CompileTimeConstantExpr).getIntValue() +} + +predicate isConstantExp (Expr e) { + // A literal is constant. + e instanceof Literal + or + // A parenthesized expression is constant if its proper expression is. + isConstantExp(e.(ParExpr).getProperExpr()) + or + e instanceof TypeAccess + or + e instanceof ArrayTypeAccess + or + e instanceof WildcardTypeAccess + or + // A binary expression is constant if both its operands are. + exists(BinaryExpr b | b = e | + isConstantExp(b.getLeftOperand()) and + isConstantExp(b.getRightOperand()) + ) + or + // A cast expression is constant if its expression is. + exists(CastExpr c | c = e | isConstantExp(c.getExpr().getProperExpr())) + or + // Multiplication by 0 is constant. + exists(MulExpr m | m = e | eval(m.getAnOperand().getProperExpr()) = 0) + or + // Integer remainder by 1 is constant. + exists(RemExpr r | r = e | + r.getLeftOperand().getType() instanceof IntegralType and + eval(r.getRightOperand().getProperExpr()) = 1 + ) + or + // Left shift by 64 (longs) or 32 (all other integer types) is constant. + exists(LShiftExpr s | s = e | + exists(IntegralType t | t = s.getLeftOperand().getType() | + eval(s.getRightOperand().getProperExpr()) = integralTypeWidth(t) + ) + ) + or + exists(AndBitwiseExpr a | a = e | + eval(a.getAnOperand().getProperExpr()) = 0 + ) + or + exists(AndLogicalExpr a | a = e | + a.getAnOperand().getProperExpr().(BooleanLiteral).getBooleanValue() = false + ) + or + exists(OrLogicalExpr o | o = e | + o.getAnOperand().getProperExpr().(BooleanLiteral).getBooleanValue() = true + ) +} + +from Expr e +where + isConstantExp(e) and + exists(Expr child | e.getAChildExpr() = child | + not isConstantExp(child) and + not child instanceof Annotation + ) and + not e instanceof CompileTimeConstantExpr and + // Exclude expressions that appear to be disabled deliberately (e.g. `false && ...`). + not e.(AndLogicalExpr).getAnOperand().(BooleanLiteral).getBooleanValue() = false +select e, "Expression always evaluates to the same value." diff --git a/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstantGood.java b/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstantGood.java new file mode 100644 index 00000000000..4de07511854 --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstantGood.java @@ -0,0 +1,3 @@ +public boolean isEven(int x) { + return x % 2 == 0; //Does work +} diff --git a/java/ql/src/Likely Bugs/Arithmetic/InformationLoss.qhelp b/java/ql/src/Likely Bugs/Arithmetic/InformationLoss.qhelp new file mode 100644 index 00000000000..d7ba87f4340 --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/InformationLoss.qhelp @@ -0,0 +1,51 @@ + + + + + +

    Compound assignment statements of the form x += y or +x *= y perform an implicit narrowing conversion +if the type of x is narrower than the type of y. +For example, x += y is equivalent to x = (T)(x + y), +where T is the type of x. +This can result in information loss and numeric errors such as overflows. +

    + +
    + + +

    Ensure that the type of the left-hand side of the compound assignment statement +is at least as wide as the type of the right-hand side.

    + +
    + + +

    If x is of type short and y is of type int, +the expression x + y is of type int. However, the expression x += y + is equivalent to x = (short) (x + y). The expression x + y is cast to the type of +the left-hand side of the assignment: short, possibly leading to information loss.

    + +

    To avoid implicitly narrowing the type of x + y, change the type of x to +int. Then the types of x and x + y are both int and there is no + need for an implicit cast.

    + +
    + + + +
  • + J. Bloch and N. Gafter, Java Puzzlers: Traps, Pitfalls, and Corner Cases, Puzzle 9. Addison-Wesley, 2005. +
  • +
  • + The Java Language Specification: + Compound Assignment Operators, + Narrowing Primitive Conversion. +
  • +
  • The CERT Oracle Secure Coding Standard for Java: + NUM00-J. Detect or prevent integer overflow.
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Arithmetic/InformationLoss.ql b/java/ql/src/Likely Bugs/Arithmetic/InformationLoss.ql new file mode 100644 index 00000000000..f5cd52bf35c --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/InformationLoss.ql @@ -0,0 +1,36 @@ +/** + * @name Implicit narrowing conversion in compound assignment + * @description Compound assignment statements (for example 'intvar += longvar') that implicitly + * cast a value of a wider type to a narrower type may result in information loss and + * numeric errors such as overflows. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id java/implicit-cast-in-compound-assignment + * @tags reliability + * security + * external/cwe/cwe-190 + * external/cwe/cwe-192 + * external/cwe/cwe-197 + * external/cwe/cwe-681 + */ +import semmle.code.java.arithmetic.Overflow + +class DangerousAssignOpExpr extends AssignOp { + DangerousAssignOpExpr() { + this instanceof AssignAddExpr or + this instanceof AssignMulExpr + } +} + +predicate problematicCasting(Type t, Expr e) { + e.getType().(NumType).widerThan((NumType)t) +} + +from DangerousAssignOpExpr a, Expr e +where + e = a.getSource() and + problematicCasting(a.getDest().getType(), e) +select a, + "Implicit cast of source type " + e.getType().getName() + + " to narrower destination type " + a.getDest().getType().getName() + "." diff --git a/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.java b/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.java new file mode 100644 index 00000000000..8f8670a080e --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.java @@ -0,0 +1,2 @@ +int i = 2000000000; +long j = i*i; // causes overflow \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.qhelp b/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.qhelp new file mode 100644 index 00000000000..9a3da8bd1ca --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.qhelp @@ -0,0 +1,51 @@ + + + + + +

    +An integer multiplication that is assigned +to a variable of type long or returned from a method with +return type long may cause unexpected arithmetic overflow. +

    + +
    + + +

    +Casting to type long before multiplying reduces the risk of arithmetic overflow. +

    + +
    + + +

    In the following example, the multiplication expression assigned to j causes overflow and results +in the value -1651507200 instead of 4000000000000000000.

    + + + +

    In the following example, the assignment to k correctly avoids overflow by casting +one of the operands to type long. +

    + + + +
    + + + +
  • + J. Bloch and N. Gafter, Java Puzzlers: Traps, Pitfalls, and Corner Cases, Puzzle 3. Addison-Wesley, 2005. +
  • +
  • + The Java Language Specification: + Multiplication Operator. +
  • +
  • The CERT Oracle Secure Coding Standard for Java: + NUM00-J. Detect or prevent integer overflow.
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql b/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql new file mode 100644 index 00000000000..acb99effb00 --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql @@ -0,0 +1,55 @@ +/** + * @name Result of multiplication cast to wider type + * @description Casting the result of a multiplication to a wider type instead of casting + * before the multiplication may cause overflow. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id java/integer-multiplication-cast-to-long + * @tags reliability + * security + * correctness + * types + * external/cwe/cwe-190 + * external/cwe/cwe-192 + * external/cwe/cwe-197 + * external/cwe/cwe-681 + */ +import java +import semmle.code.java.dataflow.RangeUtils +import semmle.code.java.Conversions + +/** An multiplication that does not overflow. */ +predicate small(MulExpr e) { + exists(NumType t, float lhs, float rhs, float res | t = e.getType() | + lhs = e.getLeftOperand().getProperExpr().(ConstantIntegerExpr).getIntValue() and + rhs = e.getRightOperand().getProperExpr().(ConstantIntegerExpr).getIntValue() and + lhs * rhs = res and + t.getOrdPrimitiveType().getMinValue() <= res and res <= t.getOrdPrimitiveType().getMaxValue() + ) +} + +/** + * A parent of e, but only one that roughly preserves the value - in particular, not calls. + */ +Expr getRestrictedParent(Expr e) { + result = e.getParent() and + (result instanceof ArithExpr or result instanceof ConditionalExpr or result instanceof ParExpr) +} + +from ConversionSite c, MulExpr e, NumType sourceType, NumType destType +where + // e is nested inside c, with only parents that roughly "preserve" the value + getRestrictedParent*(e) = c and + // the destination type is wider than the type of the multiplication + e.getType() = sourceType and + c.getConversionTarget() = destType and + destType.widerThan(sourceType) and + // not a trivial conversion + not c.isTrivial() and + // not an explicit conversion, which is probably intended by a user + c.isImplicit() and + // not obviously small and ok + not small(e) and + e.getEnclosingCallable().fromSource() +select c, "$@ converted to "+ destType.getName() +" by use in " + ("a " + c.kind()).regexpReplaceAll("^a ([aeiou])", "an $1") + ".", e, sourceType.getName() + " multiplication" diff --git a/java/ql/src/Likely Bugs/Arithmetic/IntMultToLongGood.java b/java/ql/src/Likely Bugs/Arithmetic/IntMultToLongGood.java new file mode 100644 index 00000000000..623aa3971b3 --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/IntMultToLongGood.java @@ -0,0 +1,2 @@ +int i = 2000000000; +long k = i*(long)i; // avoids overflow \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Arithmetic/MultiplyRemainder.qhelp b/java/ql/src/Likely Bugs/Arithmetic/MultiplyRemainder.qhelp new file mode 100644 index 00000000000..334c2af2ab7 --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/MultiplyRemainder.qhelp @@ -0,0 +1,43 @@ + + + + + +

    +Using the remainder operator % with the multiplication operator may not give you the result that +you expect unless you use parentheses. This is because the +remainder operator has the same precedence as the multiplication operator, and the operators are +left-associative.

    + +
    + + +

    +When you use the remainder operator with the multiplication operator, ensure that the expression is +evaluated as you expect. If necessary, add parentheses.

    + +
    + + +

    Consider a time in milliseconds, +represented by t. To calculate the number of milliseconds remaining after the time has been converted to whole minutes, +you might write t % 60 * 1000. However, this is equal to (t % 60) * 1000, which gives the wrong result. +Instead, the expression should be t % (60 * 1000). +

    + +
    + + + +
  • + J. Bloch and N. Gafter, Java Puzzlers: Traps, Pitfalls, and Corner Cases, Puzzle 35. Addison-Wesley, 2005. +
  • +
  • + The Java Tutorials: Operators. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Arithmetic/MultiplyRemainder.ql b/java/ql/src/Likely Bugs/Arithmetic/MultiplyRemainder.ql new file mode 100644 index 00000000000..ada97743b0c --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/MultiplyRemainder.ql @@ -0,0 +1,18 @@ +/** + * @name Multiplication of remainder + * @description Using the remainder operator with the multiplication operator without adding + * parentheses to clarify precedence may cause confusion. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/multiplication-of-remainder + * @tags maintainability + * correctness + */ +import java + +from MulExpr e +where + e.getLeftOperand() instanceof RemExpr and + e.getRightOperand().getType().hasName("int") +select e, "Result of a remainder operation multiplied by an integer." diff --git a/java/ql/src/Likely Bugs/Arithmetic/OctalLiteral.java b/java/ql/src/Likely Bugs/Arithmetic/OctalLiteral.java new file mode 100644 index 00000000000..e69de29bb2d diff --git a/java/ql/src/Likely Bugs/Arithmetic/OctalLiteral.qhelp b/java/ql/src/Likely Bugs/Arithmetic/OctalLiteral.qhelp new file mode 100644 index 00000000000..e5c164a2eaf --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/OctalLiteral.qhelp @@ -0,0 +1,45 @@ + + + + + +

    An integer literal consisting of a leading 0 digit +followed by one or more digits in the range 0-7 is an +octal literal. This can lead to two problems:

    + +
      +
    • An octal literal can be misread by a programmer as a decimal literal.
    • +
    • A programmer might accidentally start a decimal literal with a zero, so that the compiler + treats the decimal literal as an octal literal. For example, 010 is equal to + 8, not 10.
    • +
    + +
    + + +

    To avoid these problems:

    + +
      +
    • Avoid using octal literals so that programmers do not confuse them with decimal literals. + However, if you need to use octal literals, you should add a comment to each octal literal + indicating the intention to use octal literals.
    • +
    • When typing decimal literals, be careful not to begin them with a zero accidentally.
    • +
    + +
    + + + +
  • + J. Bloch and N. Gafter, Java Puzzlers: Traps, Pitfalls, and Corner Cases, Puzzle 59. Addison-Wesley, 2005. +
  • +
  • + The Java Language Specification: + Integer Literals. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Arithmetic/OctalLiteral.ql b/java/ql/src/Likely Bugs/Arithmetic/OctalLiteral.ql new file mode 100644 index 00000000000..3e27b935ab6 --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/OctalLiteral.ql @@ -0,0 +1,23 @@ +/** + * @name Use of octal values + * @description An integer literal that starts with '0' may cause a problem. If the '0' is + * intentional, a programmer may misread the literal as a decimal literal. If the '0' + * is unintentional and a decimal literal is intended, the compiler treats the + * literal as an octal literal. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/octal-literal + * @tags maintainability + * correctness + */ +import java + +from IntegerLiteral lit, string val +where + lit.getLiteral() = val and + val.regexpMatch("0[0-7][0-7]+") and + lit.getParent() instanceof BinaryExpr and + not lit.getParent() instanceof BitwiseExpr and + not lit.getParent() instanceof ComparisonExpr +select lit, "Integer literal starts with 0." diff --git a/java/ql/src/Likely Bugs/Arithmetic/RandomUsedOnce.java b/java/ql/src/Likely Bugs/Arithmetic/RandomUsedOnce.java new file mode 100644 index 00000000000..63eaedb7717 --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/RandomUsedOnce.java @@ -0,0 +1,12 @@ +public static void main(String args[]) { + // BAD: A new 'Random' object is created every time + // a pseudo-random integer is required. + int notReallyRandom = new Random().nextInt(); + int notReallyRandom2 = new Random().nextInt(); + + // GOOD: The same 'Random' object is used to generate + // two pseudo-random integers. + Random r = new Random(); + int random1 = r.nextInt(); + int random2 = r.nextInt(); +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Arithmetic/RandomUsedOnce.qhelp b/java/ql/src/Likely Bugs/Arithmetic/RandomUsedOnce.qhelp new file mode 100644 index 00000000000..739ab082803 --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/RandomUsedOnce.qhelp @@ -0,0 +1,57 @@ + + + + + +

    +A program that uses java.util.Random to generate a sequence of pseudo-random numbers should not create a new instance of Random +every time a new pseudo-random number is required (for example, new Random().nextInt()). +

    + +

    +According to the Java API specification:

    + +
    +

    If two instances of Random are created with the same seed, and the same sequence of method calls is made for each, they will generate and return +identical sequences of numbers.

    +
    + +

    The sequence of pseudo-random numbers returned by these calls depends only on the value of the seed. +If you construct a new Random object each time a pseudo-random number is needed, this does not +generate a good distribution of pseudo-random numbers, even though the parameterless Random() +constructor tries to initialize itself with a unique seed. +

    + +
    + +

    +Create a Random object once and use the same instance when generating sequences of +pseudo-random numbers (by calling nextInt, nextLong, and so on). +

    + +
    + + +

    In the following example, generating a series of pseudo-random numbers, such as notReallyRandom +and notReallyRandom2, by creating a new instance of Random each time is +unlikely to result in a good distribution of pseudo-random numbers. In contrast, generating a series +of pseudo-random numbers, such as random1 and random2, by calling +nextInt each time is likely to result in a good distribution. This is because +the numbers are based on only one Random object.

    + + + +
    + + + +
  • + Java API Documentation: + Random. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Arithmetic/RandomUsedOnce.ql b/java/ql/src/Likely Bugs/Arithmetic/RandomUsedOnce.ql new file mode 100644 index 00000000000..f54d1f60f8f --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/RandomUsedOnce.ql @@ -0,0 +1,20 @@ +/** + * @name Random used only once + * @description Creating an instance of 'Random' for each pseudo-random number required does not + * guarantee an evenly distributed sequence of random numbers. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/random-used-once + * @tags reliability + * maintainability + * external/cwe/cwe-335 + */ +import java + +from MethodAccess ma, Method random +where + random.getDeclaringType().hasQualifiedName("java.util","Random") and + ma.getMethod() = random and + ma.getQualifier() instanceof ClassInstanceExpr +select ma, "Random object created and used only once." diff --git a/java/ql/src/Likely Bugs/Arithmetic/WhitespaceContradictsPrecedence.qhelp b/java/ql/src/Likely Bugs/Arithmetic/WhitespaceContradictsPrecedence.qhelp new file mode 100644 index 00000000000..cd7915811cf --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/WhitespaceContradictsPrecedence.qhelp @@ -0,0 +1,53 @@ + + + + + +

    +Nested expressions where the spacing around operators suggests a different +grouping than that imposed by the Java operator precedence rules are problematic: +they could indicate a bug where the author of the code misunderstood the precedence +rules. Even if there is no a bug, the spacing could be confusing to people who +read the code. +

    + +
    + + +

    +Make sure that the spacing around operators reflects operator precedence, or use parentheses to +clarify grouping. +

    + +
    + + +

    +Consider the following piece of code for allocating an array: +

    +
    +    int[] buf = new int[capacity + capacity>>1];
    +
    +

    +Here, the spacing around + and >> suggests the grouping +capacity + (capacity>>1), that is, the allocated array should be 50% larger than +the given capacity. +

    +

    In fact, however, + has higher precedence than >>, so +this code allocates an array of size (capacity + capacity) >> 1, which is +the same as capacity. +

    + +
    + + + +
  • + J. Bloch and N. Gafter, Java Puzzlers: Traps, Pitfalls, and Corner Cases, Puzzle 35. Addison-Wesley, 2005. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Arithmetic/WhitespaceContradictsPrecedence.ql b/java/ql/src/Likely Bugs/Arithmetic/WhitespaceContradictsPrecedence.ql new file mode 100644 index 00000000000..6f47f6f4c06 --- /dev/null +++ b/java/ql/src/Likely Bugs/Arithmetic/WhitespaceContradictsPrecedence.ql @@ -0,0 +1,140 @@ +/** + * @name Whitespace contradicts operator precedence + * @description Nested expressions where the formatting contradicts the grouping enforced by operator precedence + * are difficult to read and may even indicate a bug. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id java/whitespace-contradicts-precedence + * @tags maintainability + * readability + * external/cwe/cwe-783 + */ + +import java + +/** + * A binary expression using the operator + * `+`, `-`, `*`, `/`, or `%`. + */ +class ArithmeticExpr extends BinaryExpr { + ArithmeticExpr() { + this instanceof AddExpr or + this instanceof SubExpr or + this instanceof MulExpr or + this instanceof DivExpr or + this instanceof RemExpr + } +} + +/** + * A binary expression using the operator + * `<<`, `>>`, or `>>>`. + */ +class ShiftExpr extends BinaryExpr { + ShiftExpr() { + this instanceof LShiftExpr or + this instanceof RShiftExpr or + this instanceof URShiftExpr + } +} + +/** + * A binary expression using the operator + * `==`, `!=`, `<`, `>`, `<=`, or `>=`. + */ +class RelationExpr extends BinaryExpr { + RelationExpr() { + this instanceof EqualityTest or + this instanceof ComparisonExpr + } +} + +/** + * A binary expression using the operator + * `&&` or `||`. + */ +class LogicalExpr extends BinaryExpr { + LogicalExpr() { + this instanceof AndLogicalExpr or + this instanceof OrLogicalExpr + } +} + +/** + * A binary expression of the form `x op y`, which is itself an operand (say, the left) of + * another binary expression `(x op y) op' y` such that `(x op y) op' y' = x op (y op' y)`, + * disregarding overflow. + */ +class AssocNestedExpr extends BinaryExpr { + AssocNestedExpr() { + exists(BinaryExpr parent, int idx | this.isNthChildOf(parent, idx) | + // `+`, `*`, `&&`, `||` and the bitwise operations are associative. + ((this instanceof AddExpr or this instanceof MulExpr or + this instanceof BitwiseExpr or this instanceof LogicalExpr) and + parent.getKind() = this.getKind()) + or + // Equality tests are associate over each other. + (this instanceof EqualityTest and parent instanceof EqualityTest) + or + // (x*y)/z = x*(y/z) + (this instanceof MulExpr and parent instanceof DivExpr and idx = 0) + or + // (x/y)%z = x/(y%z) + (this instanceof DivExpr and parent instanceof RemExpr and idx = 0) + or + // (x+y)-z = x+(y-z) + (this instanceof AddExpr and parent instanceof SubExpr and idx = 0) + ) + } +} + +/** + * A binary expression nested inside another binary expression where the inner operator "obviously" + * binds tighter than the outer one. This is obviously subjective. + */ +class HarmlessNestedExpr extends BinaryExpr { + HarmlessNestedExpr() { + exists(BinaryExpr parent | this = parent.getAChildExpr() | + (parent instanceof RelationExpr and (this instanceof ArithmeticExpr or this instanceof ShiftExpr)) + or + (parent instanceof LogicalExpr and this instanceof RelationExpr) + ) + } +} + +predicate startOfBinaryRhs(BinaryExpr expr, int line, int col) { + exists(Location rloc | rloc = expr.getRightOperand().getLocation() | + rloc.getStartLine() = line and rloc.getStartColumn() = col + ) +} + +predicate endOfBinaryLhs(BinaryExpr expr, int line, int col) { + exists(Location lloc | lloc = expr.getLeftOperand().getLocation() | + lloc.getEndLine() = line and lloc.getEndColumn() = col + ) +} + +/** Compute whitespace around the operator. */ +int operatorWS(BinaryExpr expr) { + exists(int line, int lcol, int rcol | + endOfBinaryLhs(expr, line, lcol) and + startOfBinaryRhs(expr, line, rcol) and + result = rcol - lcol + 1 - expr.getOp().length() + ) +} + +/** Find nested binary expressions where the programmer may have made a precedence mistake. */ +predicate interestingNesting(BinaryExpr inner, BinaryExpr outer) { + inner = outer.getAChildExpr() and + not inner instanceof AssocNestedExpr and + not inner instanceof HarmlessNestedExpr +} + +from BinaryExpr inner, BinaryExpr outer, int wsouter, int wsinner +where + interestingNesting(inner, outer) and + wsinner = operatorWS(inner) and wsouter = operatorWS(outer) and + wsinner % 2 = 0 and wsouter % 2 = 0 and + wsinner > wsouter +select outer, "Whitespace around nested operators contradicts precedence." diff --git a/java/ql/src/Likely Bugs/Cloning/MissingCallToSuperClone.qhelp b/java/ql/src/Likely Bugs/Cloning/MissingCallToSuperClone.qhelp new file mode 100644 index 00000000000..9f2e83ada12 --- /dev/null +++ b/java/ql/src/Likely Bugs/Cloning/MissingCallToSuperClone.qhelp @@ -0,0 +1,50 @@ + + + + + +

    +A clone method that is overridden in a subclass should call super.clone. +Not doing so causes the subclass clone to return an object of the wrong type, which violates +the contract for Cloneable. +

    + +
    + + + +

    +Every clone method should always use super.clone to construct the cloned object. This ensures that the cloned object +is ultimately constructed by Object.clone, which uses reflection to ensure that an object of the correct +runtime type is created. +

    + +
    + + +

    In the following example, the attempt to clone WrongEmployee fails because +super.clone is implemented incorrectly in its superclass WrongPerson.

    + + + +

    However, in the following modified example, the attempt to clone Employee succeeds +because super.clone is implemented correctly in its superclass Person.

    + + + +
    + + + +
  • + J. Bloch, Effective Java (second edition), Item 11. Addison-Wesley, 2008. +
  • +
  • + Java 6 API Specification: Object.clone(). +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Cloning/MissingCallToSuperClone.ql b/java/ql/src/Likely Bugs/Cloning/MissingCallToSuperClone.ql new file mode 100644 index 00000000000..9340798599e --- /dev/null +++ b/java/ql/src/Likely Bugs/Cloning/MissingCallToSuperClone.ql @@ -0,0 +1,23 @@ +/** + * @name Missing super clone + * @description A 'clone' method that is overridden in a subclass, and that does not itself call + * 'super.clone', causes calls to the subclass's 'clone' method to return an object of + * the wrong type. + * @kind problem + * @problem.severity error + * @precision medium + * @id java/missing-call-to-super-clone + * @tags reliability + * maintainability + * external/cwe/cwe-580 + */ +import java + +from CloneMethod c, CloneMethod sc +where + c.callsSuper(sc) and + c.fromSource() and + exists(sc.getBody()) and + not exists(CloneMethod ssc | sc.callsSuper(ssc)) +select sc, "This clone method does not call super.clone(), but is " + + "overridden and called $@.", c, "in a subclass" diff --git a/java/ql/src/Likely Bugs/Cloning/MissingCallToSuperCloneBad.java b/java/ql/src/Likely Bugs/Cloning/MissingCallToSuperCloneBad.java new file mode 100644 index 00000000000..8b96a0f7ef8 --- /dev/null +++ b/java/ql/src/Likely Bugs/Cloning/MissingCallToSuperCloneBad.java @@ -0,0 +1,26 @@ +class WrongPerson implements Cloneable { + private String name; + public WrongPerson(String name) { this.name = name; } + // BAD: 'clone' does not call 'super.clone'. + public WrongPerson clone() { + return new WrongPerson(this.name); + } +} + +class WrongEmployee extends WrongPerson { + public WrongEmployee(String name) { + super(name); + } + // ALMOST RIGHT: 'clone' correctly calls 'super.clone', + // but 'super.clone' is implemented incorrectly. + public WrongEmployee clone() { + return (WrongEmployee)super.clone(); + } +} + +public class MissingCallToSuperClone { + public static void main(String[] args) { + WrongEmployee e = new WrongEmployee("John Doe"); + WrongEmployee eclone = e.clone(); // Causes a ClassCastException + } +} diff --git a/java/ql/src/Likely Bugs/Cloning/MissingCallToSuperCloneGood.java b/java/ql/src/Likely Bugs/Cloning/MissingCallToSuperCloneGood.java new file mode 100644 index 00000000000..9418baac785 --- /dev/null +++ b/java/ql/src/Likely Bugs/Cloning/MissingCallToSuperCloneGood.java @@ -0,0 +1,29 @@ +class Person implements Cloneable { + private String name; + public Person(String name) { this.name = name; } + // GOOD: 'clone' correctly calls 'super.clone' + public Person clone() { + try { + return (Person)super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError("Should never happen"); + } + } +} + +class Employee extends Person { + public Employee(String name) { + super(name); + } + // GOOD: 'clone' correctly calls 'super.clone' + public Employee clone() { + return (Employee)super.clone(); + } +} + +public class MissingCallToSuperClone { + public static void main(String[] args) { + Employee e2 = new Employee("Jane Doe"); + Employee e2clone = e2.clone(); // 'clone' correctly returns an object of type 'Employee' + } +} diff --git a/java/ql/src/Likely Bugs/Cloning/MissingCloneDetails.qhelp b/java/ql/src/Likely Bugs/Cloning/MissingCloneDetails.qhelp new file mode 100644 index 00000000000..d1a3dc49f9a --- /dev/null +++ b/java/ql/src/Likely Bugs/Cloning/MissingCloneDetails.qhelp @@ -0,0 +1,36 @@ + + + +

    +The Java API documentation states that, for an object x, the general intent of the clone method is for it to +satisfy the following three properties: +

    +
      +
    • x.clone() != x (the cloned object is a different object instance)
    • +
    • x.clone().getClass() == x.getClass() (the cloned object is the same type as the source object)
    • +
    • x.clone().equals(x) (the cloned object has the same 'contents' as the source object)
    • +
    +

    +For the cloned object to be of the same type as the source object, non-final classes must call super.clone and that +call must eventually reach Object.clone, which creates an instance of the right type. If it were to create a new object +using a constructor, a subclass that does not implement the clone method returns an object of the wrong type. +In addition, all of the class's supertypes that also override clone must call super.clone. Otherwise, it never +reaches Object.clone and creates an object of the incorrect type. +

    +

    +However, as Object.clone only does a shallow copy of the fields of an object, any Cloneable objects +that have a "deep structure" (for example, objects that use an array or Collection) must take the clone that results from the call to super.clone +and assign explicitly created copies of the structure to the clone's fields. This means that the cloned instance +does not share its internal state with the source object. If it did share its internal state, any changes made in the cloned object would also affect +the internal state of the source object, probably causing unintended behavior. +

    +

    +One added complication is that clone cannot modify values in final fields, which would be already set by the call +to super.clone. Some fields must be made non-final to correctly implement the clone method. +

    + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Cloning/MissingMethodClone.qhelp b/java/ql/src/Likely Bugs/Cloning/MissingMethodClone.qhelp new file mode 100644 index 00000000000..9c60c63766b --- /dev/null +++ b/java/ql/src/Likely Bugs/Cloning/MissingMethodClone.qhelp @@ -0,0 +1,61 @@ + + + + + +

    +A class that implements Cloneable should override Object.clone. For non-trivial +objects, the Cloneable contract requires a deep copy of the object's internal state. A class +that does not have a clone method indicates that the class is breaking the contract and will have undesired behavior. +

    + +
    + + + +

    +The necessity of creating a deep copy of an object's internal state means that, for most objects, +clone must be overridden to satisfy the Cloneable contract. Implement a +clone method that properly creates the internal state of the cloned object. +

    + +

    Notable exceptions to this recommendation are:

    + +
      +
    • Classes that contain only primitive types (which will be properly cloned by Object.clone + as long as its Cloneable supertypes all call super.clone).
    • +
    • Subclasses of Cloneable classes that do not introduce new state.
    • +
    + +
    + + +

    In the following example, WrongStack does not implement clone. This means +that when ws1clone is cloned from ws1, the default clone +implementation is used. This results in operations on the ws1clone +stack affecting the ws1 stack.

    + + + +

    In the following modified example, RightStack does implement +clone. This means that when rs1clone is cloned from rs1, +operations on the rs1clone stack do not affect the rs1 stack.

    + + + +
    + + + +
  • + J. Bloch, Effective Java (second edition), Item 11. Addison-Wesley, 2008. +
  • +
  • + Java 6 API Specification: Object.clone(). +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Cloning/MissingMethodClone.ql b/java/ql/src/Likely Bugs/Cloning/MissingMethodClone.ql new file mode 100644 index 00000000000..7376ba9785f --- /dev/null +++ b/java/ql/src/Likely Bugs/Cloning/MissingMethodClone.ql @@ -0,0 +1,21 @@ +/** + * @name No clone method + * @description A class that implements 'Cloneable' but does not override the 'clone' method will + * have undesired behavior. + * @kind problem + * @problem.severity error + * @precision medium + * @id java/missing-clone-method + * @tags reliability + * maintainability + */ +import java + +from Class t, TypeCloneable cloneable +where + t.hasSupertype+(cloneable) and + not t.isAbstract() and + not exists(CloneMethod m | t.getAMethod() = m) and + exists(Field f | f.getDeclaringType() = t and not f.isStatic()) and + t.fromSource() +select t, "No clone method, yet implements Cloneable." diff --git a/java/ql/src/Likely Bugs/Cloning/MissingMethodCloneBad.java b/java/ql/src/Likely Bugs/Cloning/MissingMethodCloneBad.java new file mode 100644 index 00000000000..7375a19c0f8 --- /dev/null +++ b/java/ql/src/Likely Bugs/Cloning/MissingMethodCloneBad.java @@ -0,0 +1,46 @@ +abstract class AbstractStack implements Cloneable { + public AbstractStack clone() { + try { + return (AbstractStack) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError("Should not happen"); + } + } +} + +class WrongStack extends AbstractStack { + private static final int MAX_STACK = 10; + int[] elements = new int[MAX_STACK]; + int top = -1; + + void push(int newInt) { + elements[++top] = newInt; + } + int pop() { + return elements[top--]; + } + // BAD: No 'clone' method to create a copy of the elements. + // Therefore, the default 'clone' implementation (shallow copy) is used, which + // is equivalent to: + // + // public WrongStack clone() { + // WrongStack cloned = (WrongStack) super.clone(); + // cloned.elements = elements; // Both 'this' and 'cloned' now use the same elements. + // return cloned; + // } +} + +public class MissingMethodClone { + public static void main(String[] args) { + WrongStack ws1 = new WrongStack(); // ws1: {} + ws1.push(1); // ws1: {1} + ws1.push(2); // ws1: {1,2} + WrongStack ws1clone = (WrongStack) ws1.clone(); // ws1clone: {1,2} + ws1clone.pop(); // ws1clone: {1} + ws1clone.push(3); // ws1clone: {1,3} + System.out.println(ws1.pop()); // Because ws1 and ws1clone have the same + // elements, this prints 3 instead of 2 + } +} + + diff --git a/java/ql/src/Likely Bugs/Cloning/MissingMethodCloneGood.java b/java/ql/src/Likely Bugs/Cloning/MissingMethodCloneGood.java new file mode 100644 index 00000000000..95a674e6d3d --- /dev/null +++ b/java/ql/src/Likely Bugs/Cloning/MissingMethodCloneGood.java @@ -0,0 +1,43 @@ +abstract class AbstractStack implements Cloneable { + public AbstractStack clone() { + try { + return (AbstractStack) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError("Should not happen"); + } + } +} + +class RightStack extends AbstractStack { + private static final int MAX_STACK = 10; + int[] elements = new int[MAX_STACK]; + int top = -1; + + void push(int newInt) { + elements[++top] = newInt; + } + int pop() { + return elements[top--]; + } + + // GOOD: 'clone' method to create a copy of the elements. + public RightStack clone() { + RightStack cloned = (RightStack) super.clone(); + cloned.elements = elements.clone(); // 'cloned' has its own elements. + return cloned; + } +} + +public class MissingMethodClone { + public static void main(String[] args) { + RightStack rs1 = new RightStack(); // rs1: {} + rs1.push(1); // rs1: {1} + rs1.push(2); // rs1: {1,2} + RightStack rs1clone = rs1.clone(); // rs1clone: {1,2} + rs1clone.pop(); // rs1clone: {1} + rs1clone.push(3); // rs1clone: {1,3} + System.out.println(rs1.pop()); // Correctly prints 2 + } +} + + diff --git a/java/ql/src/Likely Bugs/Collections/ArrayIndexOutOfBounds.qhelp b/java/ql/src/Likely Bugs/Collections/ArrayIndexOutOfBounds.qhelp new file mode 100644 index 00000000000..11477633224 --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/ArrayIndexOutOfBounds.qhelp @@ -0,0 +1,42 @@ + + + + +

    +When accessing an array element, one must ensure that the index is less than +the length of the array. Using an index that is greater than or equal to the +array length causes an ArrayIndexOutOfBoundsException. +

    +
    + + +

    +Ensure that the index is less than the array length. +

    +
    + + +

    +The following example causes an ArrayIndexOutOfBoundsException in +the final loop iteration. +

    + +

    +The condition should be changed as follows to correctly guard the array access. +

    + + +
    + + + +
  • +The Java API Specification: +ArrayIndexOutOfBoundsException. +
  • + +
    + +
    diff --git a/java/ql/src/Likely Bugs/Collections/ArrayIndexOutOfBounds.ql b/java/ql/src/Likely Bugs/Collections/ArrayIndexOutOfBounds.ql new file mode 100644 index 00000000000..36c416fd96d --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/ArrayIndexOutOfBounds.ql @@ -0,0 +1,62 @@ +/** + * @name Array index out of bounds + * @description Accessing an array with an index that is greater than or equal to the + * length of the array causes an 'ArrayIndexOutOfBoundsException'. + * @kind problem + * @problem.severity error + * @precision high + * @id java/index-out-of-bounds + * @tags reliability + * correctness + * exceptions + */ + +import java +import semmle.code.java.dataflow.SSA +import semmle.code.java.dataflow.RangeAnalysis + +/** + * Holds if the index expression of `aa` is less than or equal to the array length plus `k`. + */ +predicate boundedArrayAccess(ArrayAccess aa, int k) { + exists(SsaVariable arr, Expr index, Bound b, int delta | + aa.getIndexExpr() = index and + aa.getArray() = arr.getAUse() and + bounded(index, b, delta, true, _) + | + exists(FieldAccess len | + len.getField() instanceof ArrayLengthField and + len.getQualifier() = arr.getAUse() and + b.getExpr() = len and + k = delta + ) + or + exists(ArrayCreationExpr arraycreation | + arraycreation = arr.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() + | + k = delta and + arraycreation.getDimension(0) = b.getExpr() + or + exists(int arrlen | + arraycreation.getFirstDimensionSize() = arrlen and + b instanceof ZeroBound and k = delta - arrlen + ) + ) + ) +} + +/** + * Holds if the index expression is less than or equal to the array length plus `k`, + * but not necessarily less than or equal to the array length plus `k-1`. + */ +predicate bestArrayAccessBound(ArrayAccess aa, int k) { + k = min(int k0 | boundedArrayAccess(aa, k0)) +} + +from ArrayAccess aa, int k, string kstr +where + bestArrayAccessBound(aa, k) and + k >= 0 and + if k = 0 then kstr = "" else kstr = " + " + k +select aa, + "This array access might be out of bounds, as the index might be equal to the array length" + kstr + "." diff --git a/java/ql/src/Likely Bugs/Collections/ArrayIndexOutOfBoundsBad.java b/java/ql/src/Likely Bugs/Collections/ArrayIndexOutOfBoundsBad.java new file mode 100644 index 00000000000..8644e5d7c9d --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/ArrayIndexOutOfBoundsBad.java @@ -0,0 +1,3 @@ +for (int i = 0; i <= a.length; i++) { // BAD + sum += a[i]; +} diff --git a/java/ql/src/Likely Bugs/Collections/ArrayIndexOutOfBoundsGood.java b/java/ql/src/Likely Bugs/Collections/ArrayIndexOutOfBoundsGood.java new file mode 100644 index 00000000000..a5b8694ad80 --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/ArrayIndexOutOfBoundsGood.java @@ -0,0 +1,3 @@ +for (int i = 0; i < a.length; i++) { // GOOD + sum += a[i]; +} diff --git a/java/ql/src/Likely Bugs/Collections/Containers.qll b/java/ql/src/Likely Bugs/Collections/Containers.qll new file mode 100644 index 00000000000..4c19ddf647c --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/Containers.qll @@ -0,0 +1,45 @@ +import semmle.code.java.Collections +import semmle.code.java.Maps + +/** + * Containers are an abstraction of collections and maps. + */ +class ContainerType extends RefType { + ContainerType() { + this instanceof CollectionType or + this instanceof MapType + } +} + +class ContainerMutator extends Method { + ContainerMutator() { + this instanceof CollectionMutator or + this instanceof MapMutator + } +} + +class ContainerMutation extends MethodAccess { + ContainerMutation() { + this instanceof CollectionMutation or + this instanceof MapMutation + } + + predicate resultIsChecked() { + this.(CollectionMutation).resultIsChecked() or + this.(MapMutation).resultIsChecked() + } +} + +class ContainerQueryMethod extends Method { + ContainerQueryMethod() { + this instanceof CollectionQueryMethod or + this instanceof MapQueryMethod + } +} + +class FreshContainer extends ClassInstanceExpr { + FreshContainer() { + this instanceof FreshCollection or + this instanceof FreshMap + } +} diff --git a/java/ql/src/Likely Bugs/Collections/ContainsTypeMismatch.java b/java/ql/src/Likely Bugs/Collections/ContainsTypeMismatch.java new file mode 100644 index 00000000000..d375d39ca1f --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/ContainsTypeMismatch.java @@ -0,0 +1,5 @@ +void m(List list) { + if (list.contains(123)) { // Call 'contains' with non-string argument (without quotation marks) + // ... + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Collections/ContainsTypeMismatch.qhelp b/java/ql/src/Likely Bugs/Collections/ContainsTypeMismatch.qhelp new file mode 100644 index 00000000000..8f5c2ef1a1b --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/ContainsTypeMismatch.qhelp @@ -0,0 +1,59 @@ + + + + + +

    The contains method of the Collection interface +has an argument of type Object. Therefore, you can try to check if an object +of any type is a member of a collection, regardless of the collection's element type. However, +although you can call contains with an argument of a different type than that of the collection, +it is unlikely that the collection actually +contains an object of this type. +

    + +

    Similar considerations apply to other container access methods, such as Map.get, +where the argument may also have type Object. +

    + +
    + + +

    Ensure that you use the correct argument with a call to contains. +

    + +
    + + +

    In the following example, although the argument to contains is an integer, the code +does not result in a type error because the argument does not have to match the type of the elements +of list. However, the argument is unlikely to be found (and the body of the +if statement is therefore not executed), so it is probably a typographical error: the +argument should be enclosed in quotation marks. +

    + + + +

    +Note that you must take particular care when working with collections over boxed types, +as illustrated in the following example. The first call to contains returns false +because you cannot compare two boxed numeric primitives of different types, in this case Short(1) +(in set) and Integer(1) (the argument). The second call to contains +returns true because you can compare Short(1) and Short(1). +

    + + + +
    + + + +
  • + Java API Documentation: + Collection.contains. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Collections/ContainsTypeMismatch.ql b/java/ql/src/Likely Bugs/Collections/ContainsTypeMismatch.ql new file mode 100644 index 00000000000..7c48b8a0ffc --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/ContainsTypeMismatch.ql @@ -0,0 +1,75 @@ +/** + * @name Type mismatch on container access + * @description Calling container access methods such as 'Collection.contains' + * or 'Map.get' with an object of a type that is incompatible with + * the corresponding container element type is unlikely to return 'true'. + * @kind problem + * @problem.severity error + * @precision very-high + * @id java/type-mismatch-access + * @tags reliability + * correctness + * logic + */ +import java +import semmle.code.java.Collections + +predicate containerAccess(string package, string type, int p, string signature, int i) { + package = "java.util" and type = "Collection" and p = 0 and signature = "contains(java.lang.Object)" and i = 0 or + package = "java.util" and type = "Dictionary" and p = 0 and signature = "get(java.lang.Object)" and i = 0 or + package = "java.util" and type = "Hashtable" and p = 1 and signature = "contains(java.lang.Object)" and i = 0 or + package = "java.util" and type = "List" and p = 0 and signature = "indexOf(java.lang.Object)" and i = 0 or + package = "java.util" and type = "List" and p = 0 and signature = "lastIndexOf(java.lang.Object)" and i = 0 or + package = "java.util" and type = "Map" and p = 0 and signature = "get(java.lang.Object)" and i = 0 or + package = "java.util" and type = "Map" and p = 0 and signature = "getOrDefault(java.lang.Object,java.lang.Object)" and i = 0 or + package = "java.util" and type = "Map" and p = 0 and signature = "containsKey(java.lang.Object)" and i = 0 or + package = "java.util" and type = "Map" and p = 1 and signature = "containsValue(java.lang.Object)" and i = 0 or + package = "java.util" and type = "Stack" and p = 0 and signature = "search(java.lang.Object)" and i = 0 or + package = "java.util" and type = "Vector" and p = 0 and signature = "indexOf(java.lang.Object,int)" and i = 0 or + package = "java.util" and type = "Vector" and p = 0 and signature = "lastIndexOf(java.lang.Object,int)" and i = 0 or + package = "java.util.concurrent" and type = "ConcurrentHashMap" and p = 1 and signature = "contains(java.lang.Object)" and i = 0 +} + +class MismatchedContainerAccess extends MethodAccess { + MismatchedContainerAccess() { + exists(string package, string type, int i | + containerAccess(package, type, _, getCallee().getSignature(), i) + | + getCallee().getDeclaringType().getASupertype*().getSourceDeclaration().hasQualifiedName(package, type) and + getCallee().getParameter(i).getType() instanceof TypeObject + ) + } + + /** + * Holds if `result` is one of the element types of the container accessed by this call + * and the argument at index `i` of this call is expected to have type `result`. + */ + RefType getReceiverElementType(int i) { + exists(RefType t, GenericType g, string package, string type, int p | + containerAccess(package, type, p, getCallee().getSignature(), i) + | + t = getCallee().getDeclaringType() and + t.getASupertype*().getSourceDeclaration() = g and + g.hasQualifiedName(package, type) and + indirectlyInstantiates(t, g, p, result) + ) + } + + /** + * Gets the type of the argument at index `i` of this method access, + * boxed if it is a primitive. + */ + RefType getArgumentType(int i) { + exists(Type argtp | argtp = this.getArgument(i).getType() | + result = argtp or result = argtp.(PrimitiveType).getBoxedType() + ) + } +} + +from MismatchedContainerAccess ma, RefType typearg, RefType argtype, int idx +where + typearg = ma.getReceiverElementType(idx).getSourceDeclaration() and + argtype = ma.getArgumentType(idx) and + not haveIntersection(typearg, argtype) +select ma.getArgument(idx), "Actual argument type '" + argtype.getName() + "'" + + " is incompatible with expected argument type '" + typearg.getName() + "'." diff --git a/java/ql/src/Likely Bugs/Collections/ContainsTypeMismatch2.java b/java/ql/src/Likely Bugs/Collections/ContainsTypeMismatch2.java new file mode 100644 index 00000000000..103948b23e1 --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/ContainsTypeMismatch2.java @@ -0,0 +1,9 @@ +HashSet set = new HashSet(); +short s = 1; +set.add(s); +// Following statement prints 'false', because the argument is a literal int, which is auto-boxed +// to an Integer +System.out.println(set.contains(1)); +// Following statement prints 'true', because the argument is a literal int that is cast to a short, +// which is auto-boxed to a Short +System.out.println(set.contains((short)1)); diff --git a/java/ql/src/Likely Bugs/Collections/IteratorRemoveMayFail.java b/java/ql/src/Likely Bugs/Collections/IteratorRemoveMayFail.java new file mode 100644 index 00000000000..2b248f66b5f --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/IteratorRemoveMayFail.java @@ -0,0 +1,22 @@ +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +public class A { + private List l; + + public A(Integer... is) { + this.l = Arrays.asList(is); + } + + public List getList() { + return l; + } + + public static void main(String[] args) { + A a = new A(23, 42); + for (Iterator iter = a.getList().iterator(); iter.hasNext();) + if (iter.next()%2 != 0) + iter.remove(); + } +} diff --git a/java/ql/src/Likely Bugs/Collections/IteratorRemoveMayFail.qhelp b/java/ql/src/Likely Bugs/Collections/IteratorRemoveMayFail.qhelp new file mode 100644 index 00000000000..6e0c3cd047d --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/IteratorRemoveMayFail.qhelp @@ -0,0 +1,49 @@ + + + +

    +The remove method of the Iterator interface is an optional operation. +It is not supported by iterators on unmodifiable collections, or iterators on lists constructed +by the Arrays.asList method. Invoking remove on such an iterator +will lead to an UnsupportedOperationException. +

    + +
    + + +

    +If a collection is meant to be modified after construction, use a modifiable collection type such as +ArrayList or HashSet. +

    + +
    + + +

    +In the following example, the constructor A(Integer...) initializes the field +A.l to Arrays.asList(is). While the type of lists returned by +Arrays.asList supports element updates through the set method, it +does not support element removal. Hence the call to iter.remove on line 20 must +fail at runtime. +

    + + + +

    +To avoid this failure, copy the list returned by Arrays.asList into a newly +created ArrayList like this: +

    + + + +
    + + + +
  • Mark Needham: Java: Fooled by java.util.Arrays.asList.
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Collections/IteratorRemoveMayFail.ql b/java/ql/src/Likely Bugs/Collections/IteratorRemoveMayFail.ql new file mode 100644 index 00000000000..eccb0aeebdc --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/IteratorRemoveMayFail.ql @@ -0,0 +1,66 @@ +/** + * @name Call to Iterator.remove may fail + * @description Attempting to invoke 'Iterator.remove' on an iterator over a collection that does not + * support element removal causes a runtime exception. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/iterator-remove-failure + * @tags reliability + * correctness + * logic + */ + +import java + +class SpecialCollectionCreation extends MethodAccess { + SpecialCollectionCreation() { + exists(Method m, RefType rt | m = this.(MethodAccess).getCallee() and rt = m.getDeclaringType() | + (rt.hasQualifiedName("java.util", "Arrays") and m.hasName("asList")) or + (rt.hasQualifiedName("java.util", "Collections") and m.getName().regexpMatch("singleton.*|unmodifiable.*")) + ) + } +} + +predicate containsSpecialCollection(Expr e, SpecialCollectionCreation origin) { + e = origin or + exists(Variable v | + containsSpecialCollection(v.getAnAssignedValue(), origin) and + e = v.getAnAccess() + ) or + exists(Call c, int i | + containsSpecialCollection(c.getArgument(i), origin) and + e = c.getCallee().getParameter(i).getAnAccess() + ) or + exists(Call c, ReturnStmt r | e = c | + r.getEnclosingCallable() = c.getCallee() and + containsSpecialCollection(r.getResult(), origin) + ) +} + +predicate iterOfSpecialCollection(Expr e, SpecialCollectionCreation origin) { + exists(MethodAccess ma | ma = e | + containsSpecialCollection(ma.getQualifier(), origin) and + ma.getCallee().hasName("iterator") + ) or + exists(Variable v | + iterOfSpecialCollection(v.getAnAssignedValue(), origin) and + e = v.getAnAccess() + ) or + exists(Call c, int i | + iterOfSpecialCollection(c.getArgument(i), origin) and + e = c.getCallee().getParameter(i).getAnAccess() + ) or + exists(Call c, ReturnStmt r | e = c | + r.getEnclosingCallable() = c.getCallee() and + iterOfSpecialCollection(r.getResult(), origin) + ) +} + +from MethodAccess remove, SpecialCollectionCreation scc +where + remove.getCallee().hasName("remove") and + iterOfSpecialCollection(remove.getQualifier(), scc) +select remove, + "This call may fail when iterating over the collection created $@, since it does not support element removal.", + scc, "here" diff --git a/java/ql/src/Likely Bugs/Collections/IteratorRemoveMayFailGood.java b/java/ql/src/Likely Bugs/Collections/IteratorRemoveMayFailGood.java new file mode 100644 index 00000000000..e36b3cbb7d0 --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/IteratorRemoveMayFailGood.java @@ -0,0 +1,23 @@ +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.ArrayList; + +public class A { + private List l; + + public A(Integer... is) { + this.l = new ArrayList(Arrays.asList(is)); + } + + public List getList() { + return l; + } + + public static void main(String[] args) { + A a = new A(23, 42); + for (Iterator iter = a.getList().iterator(); iter.hasNext();) + if (iter.next()%2 != 0) + iter.remove(); + } +} diff --git a/java/ql/src/Likely Bugs/Collections/ReadOnlyContainer.java b/java/ql/src/Likely Bugs/Collections/ReadOnlyContainer.java new file mode 100644 index 00000000000..72cf7892848 --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/ReadOnlyContainer.java @@ -0,0 +1,8 @@ +boolean containsDuplicates(Object[] array) { + java.util.Set seen = new java.util.HashSet(); + for (Object o : array) { + if (seen.contains(o)) + return true; + } + return false; +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Collections/ReadOnlyContainer.qhelp b/java/ql/src/Likely Bugs/Collections/ReadOnlyContainer.qhelp new file mode 100644 index 00000000000..2e8aa2d2539 --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/ReadOnlyContainer.qhelp @@ -0,0 +1,56 @@ + + + + +

    +A method that queries the contents of a collection or map (such as containsKey +or isEmpty) is invoked on an object that is known to be empty. Such method +calls do not return interesting results, and may indicate missing code or a logic error. +

    + +
    + + +

    +Either remove the collection/map if it is unnecessary, or ensure that it contains the +elements it was meant to contain. +

    + +
    + + +

    +The following example code iterates over an array of objects to determine whether it contains +duplicate elements. It maintains a collection seen, which is intended to contain +all the elements seen so far in traversing the array. If the current element is already +contained in that collection then the method returns true, indicating that a +duplicate has been found. +

    + +

    +Note, however, that no elements are ever actually added to seen, so the method always +returns false. +

    + + + +

    +To fix this problem, a statement seen.add(o); should be added to the end of the loop +body to ensure that seen is correctly maintained. +

    + +
    + + + +
  • + Java API Documentation: + Collection, + Map. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Collections/ReadOnlyContainer.ql b/java/ql/src/Likely Bugs/Collections/ReadOnlyContainer.ql new file mode 100644 index 00000000000..0d0dc5da229 --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/ReadOnlyContainer.ql @@ -0,0 +1,43 @@ +/** + * @name Container contents are never initialized + * @description Querying the contents of a collection or map that is never initialized is not normally useful. + * @kind problem + * @problem.severity error + * @precision very-high + * @id java/empty-container + * @tags reliability + * maintainability + * useless-code + * external/cwe/cwe-561 + */ + +import java +import semmle.code.java.Reflection +import Containers + +from Variable v +where + v.fromSource() and + v.getType() instanceof ContainerType and + // Exclude parameters and non-private fields. + (v instanceof LocalVariableDecl or v.(Field).isPrivate()) and + // Exclude fields that may be written to reflectively. + not reflectivelyWritten(v) and + // Every access to `v` is either... + forall(VarAccess va | va = v.getAnAccess() | + // ...an assignment storing a fresh container into `v`, + exists(AssignExpr assgn | va = assgn.getDest() | assgn.getSource() instanceof FreshContainer) or + /// ...a return (but only if `v` is a local variable) + (v instanceof LocalVariableDecl and exists(ReturnStmt ret | ret.getResult() = va)) or + // ...or a call to a query method on `v`. + exists(MethodAccess ma | va = ma.getQualifier() | ma.getMethod() instanceof ContainerQueryMethod) + ) and + // There is at least one call to a query method. + exists(MethodAccess ma | v.getAnAccess() = ma.getQualifier() | + ma.getMethod() instanceof ContainerQueryMethod + ) and + // Also, any value that `v` is initialized to is a fresh container, + forall(Expr e | e = v.getAnAssignedValue() | e instanceof FreshContainer) and + // and `v` is not implicitly initialized by a for-each loop. + not exists(EnhancedForStmt efs | efs.getVariable().getVariable() = v) +select v, "The contents of this container are never initialized." diff --git a/java/ql/src/Likely Bugs/Collections/RemoveTypeMismatch.java b/java/ql/src/Likely Bugs/Collections/RemoveTypeMismatch.java new file mode 100644 index 00000000000..593eb89fb32 --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/RemoveTypeMismatch.java @@ -0,0 +1,5 @@ +void m(List list) { + if (list.remove(123)) { // Call 'remove' with non-string argument (without quotation marks) + // ... + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Collections/RemoveTypeMismatch.qhelp b/java/ql/src/Likely Bugs/Collections/RemoveTypeMismatch.qhelp new file mode 100644 index 00000000000..c9965746ff3 --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/RemoveTypeMismatch.qhelp @@ -0,0 +1,61 @@ + + + + + +

    The remove method of the Collection interface +has an argument of type Object. Therefore, you can try to remove an object +of any type from a collection, regardless of the collection's element type. However, +although you can call remove with an argument of a different type than that of the collection, +it is unlikely that the collection actually +contains an object of this type. +

    + +

    Similar considerations apply to other container modification methods, such as Map.remove, +where the argument may also have type Object. +

    + +
    + + +

    Ensure that you use the correct argument with a call to remove. +

    + +
    + + +

    In the following example, although the argument to contains is an integer, the code +does not result in a type error because the argument to remove does not have to match +the type of the elements of list. However, the argument is unlikely to be found and +removed (and the body of the if statement is therefore not executed), so it is probably +a typographical error: the argument should be enclosed in quotation marks. +

    + + + +

    +Note that you must take particular care when working with collections over boxed types, +as illustrated in the following example. The first call to remove fails +because you cannot compare two boxed numeric primitives of different types, in this case Short(1) +(in set) and Integer(1) (the argument). Therefore, remove +cannot find the item to remove. The second call to remove succeeds because you can +compare Short(1) and Short(1). Therefore, remove can find the +item to remove. +

    + + + +
    + + + +
  • + Java API Documentation: + Collection.remove. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Collections/RemoveTypeMismatch.ql b/java/ql/src/Likely Bugs/Collections/RemoveTypeMismatch.ql new file mode 100644 index 00000000000..7285713714f --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/RemoveTypeMismatch.ql @@ -0,0 +1,70 @@ +/** + * @name Type mismatch on container modification + * @description Calling container modification methods such as 'Collection.remove' + * or 'Map.remove' with an object of a type that is incompatible with + * the corresponding container element type is unlikely to have any effect. + * @kind problem + * @problem.severity error + * @precision very-high + * @id java/type-mismatch-modification + * @tags reliability + * correctness + * logic + */ +import java +import semmle.code.java.Collections + +predicate containerModification(string package, string type, int p, string signature, int i) { + package = "java.util" and type = "Collection" and p = 0 and signature = "remove(java.lang.Object)" and i = 0 or + package = "java.util" and type = "Deque" and p = 0 and signature = "removeFirstOccurrence(java.lang.Object)" and i = 0 or + package = "java.util" and type = "Deque" and p = 0 and signature = "removeLastOccurrence(java.lang.Object)" and i = 0 or + package = "java.util" and type = "Dictionary" and p = 0 and signature = "remove(java.lang.Object)" and i = 0 or + package = "java.util" and type = "Map" and p = 0 and signature = "remove(java.lang.Object)" and i = 0 or + package = "java.util" and type = "Map" and p = 0 and signature = "remove(java.lang.Object,java.lang.Object)" and i = 0 or + package = "java.util" and type = "Map" and p = 1 and signature = "remove(java.lang.Object,java.lang.Object)" and i = 1 or + package = "java.util" and type = "Vector" and p = 0 and signature = "removeElement(java.lang.Object)" and i = 0 +} + +class MismatchedContainerModification extends MethodAccess { + MismatchedContainerModification() { + exists(string package, string type, int i | + containerModification(package, type, _, getCallee().getSignature(), i) + | + getCallee().getDeclaringType().getASupertype*().getSourceDeclaration().hasQualifiedName(package, type) and + getCallee().getParameter(i).getType() instanceof TypeObject + ) + } + + /** + * Holds if `result` is one of the element types of the container accessed by this call + * and the argument at index `i` of this call is expected to have type `result`. + */ + RefType getReceiverElementType(int i) { + exists(RefType t, GenericType g, string package, string type, int p | + containerModification(package, type, p, getCallee().getSignature(), i) + | + t = getCallee().getDeclaringType() and + t.getASupertype*().getSourceDeclaration() = g and + g.hasQualifiedName(package, type) and + indirectlyInstantiates(t, g, p, result) + ) + } + + /** + * Gets the type of the argument at index `i` of this method access, + * boxed if it is a primitive. + */ + RefType getArgumentType(int i) { + exists(Type argtp | argtp = this.getArgument(i).getType() | + result = argtp or result = argtp.(PrimitiveType).getBoxedType() + ) + } +} + +from MismatchedContainerModification ma, RefType elementtype, RefType argtype, int idx +where + elementtype = ma.getReceiverElementType(idx).getSourceDeclaration() and + argtype = ma.getArgumentType(idx) and + not haveIntersection(elementtype, argtype) +select ma.getArgument(idx), "Actual argument type '" + argtype.getName() + "'" + + " is incompatible with expected argument type '" + elementtype.getName() + "'." diff --git a/java/ql/src/Likely Bugs/Collections/RemoveTypeMismatch2.java b/java/ql/src/Likely Bugs/Collections/RemoveTypeMismatch2.java new file mode 100644 index 00000000000..d2f0782ab9f --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/RemoveTypeMismatch2.java @@ -0,0 +1,11 @@ +HashSet set = new HashSet(); +short s = 1; +set.add(s); +// Following statement fails, because the argument is a literal int, which is auto-boxed +// to an Integer +set.remove(1); +System.out.println(set); // Prints [1] +// Following statement succeeds, because the argument is a literal int that is cast to a short, +// which is auto-boxed to a Short +set.remove((short)1); +System.out.println(set); // Prints [] \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.java b/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.java new file mode 100644 index 00000000000..901a5e3b1a0 --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.java @@ -0,0 +1,12 @@ +private Set reachableNodes = new HashSet(); + +boolean reachable(Node n) { + boolean reachable; + if (n == ROOT) + reachable = true; + else + reachable = reachable(n.getParent()); + if (reachable) + reachableNodes.add(n); + return reachable; +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.qhelp b/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.qhelp new file mode 100644 index 00000000000..3b39a4f4bed --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.qhelp @@ -0,0 +1,53 @@ + + + + +

    +If the contents of a collection or map are never accessed in any way, then it is useless and +the code that updates it is effectively dead code. Often, such objects are left over from +an incomplete refactoring, or they indicate an underlying logic error. +

    + +
    + + +

    +Either remove the collection/map if it is genuinely unnecessary, or ensure that its +elements are accessed. +

    + +
    + + +

    +In the following example code, the reachable method determines whether a node in +a tree is reachable from ROOT. It maintains a set reachableNodes, +which contains all nodes that have previously been found to be reachable. Most likely, this set +is meant to act as a cache to avoid spurious recomputation, but as it stands the code never checks +whether any node is contained in the set. +

    + + + +

    +In the following modification of the above example, reachable checks the cache +to see whether the node has already been considered. +

    + + + +
    + + + +
  • + Java API Documentation: + Collection, + Map. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.ql b/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.ql new file mode 100644 index 00000000000..6ba317b93e6 --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/WriteOnlyContainer.ql @@ -0,0 +1,41 @@ +/** + * @name Container contents are never accessed + * @description A collection or map whose contents are never queried or accessed is useless. + * @kind problem + * @problem.severity error + * @precision very-high + * @id java/unused-container + * @tags maintainability + * useless-code + * external/cwe/cwe-561 + */ + +import java +import semmle.code.java.Reflection +import semmle.code.java.frameworks.Lombok +import Containers + +from Variable v +where + v.fromSource() and + v.getType() instanceof ContainerType and + // Exclude parameters and non-private fields. + (v instanceof LocalVariableDecl or v.(Field).isPrivate()) and + // Exclude fields that may be read from reflectively. + not reflectivelyRead(v) and + // Exclude fields annotated with `@SuppressWarnings("unused")`. + not v.getAnAnnotation().(SuppressWarningsAnnotation).getASuppressedWarning() = "\"unused\"" and + // Exclude fields with relevant Lombok annotations. + not v instanceof LombokGetterAnnotatedField and + // Every access to `v` is either... + forex(VarAccess va | va = v.getAnAccess() | + // ...an assignment storing a new container into `v`, + exists(AssignExpr assgn | va = assgn.getDest() and assgn.getSource() instanceof ClassInstanceExpr) or + // ...or a call to a mutator method on `v` such that the result of the call is not checked. + exists(ContainerMutation cm | va = cm.getQualifier() and not cm.resultIsChecked()) + ) and + // Also, any value that `v` is initialized to is a new container, + forall(Expr e | e = v.getAnAssignedValue() | e instanceof ClassInstanceExpr) and + // and `v` is not implicitly initialized by a for-each loop. + not exists(EnhancedForStmt efs | efs.getVariable().getVariable() = v) +select v, "The contents of this container are never accessed." diff --git a/java/ql/src/Likely Bugs/Collections/WriteOnlyContainerGood.java b/java/ql/src/Likely Bugs/Collections/WriteOnlyContainerGood.java new file mode 100644 index 00000000000..6366bd688d5 --- /dev/null +++ b/java/ql/src/Likely Bugs/Collections/WriteOnlyContainerGood.java @@ -0,0 +1,15 @@ +private Set reachableNodes = new HashSet(); + +boolean reachable(Node n) { + if (reachableNodes.contains(n)) + return true; + + boolean reachable; + if (n == ROOT) + reachable = true; + else + reachable = reachable(n.getParent()); + if (reachable) + reachableNodes.add(n); + return reachable; +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Comparison/BitwiseSignCheck.java b/java/ql/src/Likely Bugs/Comparison/BitwiseSignCheck.java new file mode 100644 index 00000000000..2df173000a3 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/BitwiseSignCheck.java @@ -0,0 +1,4 @@ +int x = -1; +int n = 31; + +boolean bad = (x & (1< 0; \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Comparison/BitwiseSignCheck.qhelp b/java/ql/src/Likely Bugs/Comparison/BitwiseSignCheck.qhelp new file mode 100644 index 00000000000..6a80a0caaa6 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/BitwiseSignCheck.qhelp @@ -0,0 +1,46 @@ + + + + + +

    Checking whether the result of a bitwise operation is greater than zero may yield unexpected results. +

    + +
    + + +

    It is more robust to check whether the result of the bitwise operation is non-zero.

    + +
    + + +

    In the following example, the expression assigned to variable bad is not a robust way to check +that the nth bit of x is set. +With the given values of x (all bits are set) and n, +the expression x & (1<<n) has the value -2147483648, +and the variable bad is assigned false, +even though the 31st bit of x is, in fact, set. +

    + + + +

    In the following example, the expression assigned to variable good is a robust way to check that the +nth bit of x is set. With the given values of x and +n, the variable good is assigned true.

    + + + +
    + + + +
  • + The Java Language Specification: + Integer Bitwise Operators &, ^, and |. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Comparison/BitwiseSignCheck.ql b/java/ql/src/Likely Bugs/Comparison/BitwiseSignCheck.ql new file mode 100644 index 00000000000..d3945141018 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/BitwiseSignCheck.ql @@ -0,0 +1,18 @@ +/** + * @name Sign check of bitwise operation + * @description Checking the sign of the result of a bitwise operation may yield unexpected results. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/bitwise-sign-check + * @tags reliability + * correctness + */ +import java + +from ComparisonExpr e +where + e.isStrict() and + e.getGreaterOperand() instanceof BitwiseExpr and + e.getLesserOperand().(IntegerLiteral).getIntValue() = 0 +select e, "Sign check of a bitwise operation." diff --git a/java/ql/src/Likely Bugs/Comparison/BitwiseSignCheckGood.java b/java/ql/src/Likely Bugs/Comparison/BitwiseSignCheckGood.java new file mode 100644 index 00000000000..96d2b1a045d --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/BitwiseSignCheckGood.java @@ -0,0 +1,4 @@ +int x = -1; +int n = 31; + +boolean good = (x & (1< + + + + +

    If two identical expressions are compared (that is, checked for equality or +inequality), this is typically an +indication of a mistake, because the Boolean value of the comparison +is always the same. Often, it indicates that the wrong qualifier has been +used on a field access.

    + +

    An exception applies to inequality (!=) and equality (==) tests +of a floating point variable with itself: the special floating point value NaN ("not-a-number") +is the only value that is not considered to be equal to itself. Thus, the test x != x where +x is a float or double variable is equivalent to checking whether +x is NaN, and similarly for x == x.

    + +
    + + +

    It is never good practice to compare a value with itself. If you require constant +behavior, use the Boolean literals true and +false, rather than encoding them obscurely as 1 == 1 +or similar.

    + +

    If an inequality test (using !=) of a floating point variable with itself is intentional, it +should be replaced by Double.isNaN(...) or Float.isNaN(...) for readability. +Similarly, if an equality test (using ==) of a floating point variable with itself is intentional, it +should be replaced by !Double.isNaN(...) or !Float.isNaN(...).

    + +
    + + +

    In the example below, the original version of Customer compares id with +id, which always returns true. The corrected version of Customer +includes the missing qualifier o in the comparison of id with o.id.

    + + + +
    + + + +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • +
  • +Java Language Specification: +15.21.1. Numerical Equality Operators. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Comparison/CompareIdenticalValues.ql b/java/ql/src/Likely Bugs/Comparison/CompareIdenticalValues.ql new file mode 100644 index 00000000000..bb27663f69e --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/CompareIdenticalValues.ql @@ -0,0 +1,83 @@ +/** + * @name Comparison of identical values + * @description If the same expression occurs on both sides of a comparison + * operator, the operator is redundant, and probably indicates a mistake. + * @kind problem + * @problem.severity error + * @precision very-high + * @id java/comparison-of-identical-expressions + * @tags reliability + * correctness + * logic + */ + +import java + +predicate comparison(BinaryExpr binop, Expr left, Expr right) { + (binop instanceof ComparisonExpr or binop instanceof EqualityTest) and + binop.getLeftOperand() = left and binop.getRightOperand() = right +} + +/** + * Are `left` and `right` in syntactic positions where we might want to compare + * them for structural equality? + * + * Note that this is an overapproximation: it only checks that `left` and + * `right` are at the same depth below a comparison. + */ +predicate toCompare(Expr left, Expr right) { + comparison(_, left, right) or + toCompare(left.getParent(), right.getParent()) +} + +pragma[noinline] +predicate varsToCompare(VarAccess left, VarAccess right, Variable v1, Variable v2) { + toCompare(left, right) and + left.getVariable() = v1 and + right.getVariable() = v2 +} + +/** Are `left` and `right` accesses to `v` on the same object? */ +predicate sameVariable(VarAccess left, VarAccess right, Variable v) { + varsToCompare(left, right, v, v) and + ( + sameVariable(left.getQualifier(), right.getQualifier(), _) or + left.isLocal() and right.isLocal() + ) +} + +/** Are `left` and `right` structurally equal? */ +predicate equal(Expr left, Expr right) { + toCompare(left, right) and + ( + left.(Literal).getValue() = right.(Literal).getValue() or + sameVariable(left, right, _) or + exists(BinaryExpr bLeft, BinaryExpr bRight | bLeft = left and bRight = right | + bLeft.getKind() = bRight.getKind() and + equal(bLeft.getLeftOperand(), bRight.getLeftOperand()) and + equal(bLeft.getRightOperand(), bRight.getRightOperand()) + ) + ) +} + +predicate specialCase(EqualityTest comparison, string msg) { + exists(FloatingPointType fptp, string neg, string boxedName | + fptp = comparison.getAnOperand().getType() and + // Name of boxed type corresponding to `fptp`. + (if fptp.getName().toLowerCase() = "float" then boxedName = "Float" else boxedName = "Double") and + // Equality tests are tests for not-`NaN`, inequality tests for `NaN`. + (if comparison instanceof EQExpr then neg = "!" else neg = "") and + msg = "equivalent to " + neg + boxedName + ".isNaN(" + comparison.getLeftOperand() + ")" + ) +} + +from BinaryExpr comparison, Expr left, Expr right, string msg, string equiv +where + comparison(comparison, left, right) and + equal(left, right) and + ( + specialCase(comparison, equiv) and msg = "Comparison is " + equiv + or + not specialCase(comparison, _) and msg = "Comparison of identical values " + left + " and " + right and equiv="" + ) +select comparison, msg + "." diff --git a/java/ql/src/Likely Bugs/Comparison/CovariantCompareTo.java b/java/ql/src/Likely Bugs/Comparison/CovariantCompareTo.java new file mode 100644 index 00000000000..4041e8a2454 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/CovariantCompareTo.java @@ -0,0 +1,19 @@ +public class CovariantCompareTo { + static class Super implements Comparable { + public int compareTo(Super rhs) { + return -1; + } + } + + static class Sub extends Super { + public int compareTo(Sub rhs) { // Definition of compareTo uses a different parameter type + return 0; + } + } + + public static void main(String[] args) { + Super a = new Sub(); + Super b = new Sub(); + System.out.println(a.compareTo(b)); + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Comparison/CovariantCompareTo.qhelp b/java/ql/src/Likely Bugs/Comparison/CovariantCompareTo.qhelp new file mode 100644 index 00000000000..fa073be74a0 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/CovariantCompareTo.qhelp @@ -0,0 +1,45 @@ + + + + + +

    Classes that implement Comparable<T> and define +a compareTo method whose parameter type is not T +overload the compareTo method instead of overriding it. This may not be intended. +

    + +
    + + +

    In the following example, the call to compareTo on line 17 calls the method +defined in class Super, instead of the method defined in class Sub, because +the type of a and b is Super. This may not be the method that +the programmer intended.

    + + + +
    + + +

    To override the Comparable<T>.compareTo method, +the parameter of compareTo must have type T. +

    + +

    In the example above, this means that the type of the parameter of Sub.compareTo should be changed +to Super.

    + +
    + + + +
  • J. Bloch, Effective Java (second edition), Item 12. Addison-Wesley, 2008.
  • +
  • The Java Language Specification: + Overriding (by Instance Methods), + Overloading.
  • +
  • The Java Tutorials: Overriding and Hiding Methods.
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Comparison/CovariantCompareTo.ql b/java/ql/src/Likely Bugs/Comparison/CovariantCompareTo.ql new file mode 100644 index 00000000000..f54b64ec512 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/CovariantCompareTo.ql @@ -0,0 +1,56 @@ +/** + * @name Overloaded compareTo + * @description Defining 'Comparable.compareTo', where the parameter of 'compareTo' is not of the + * appropriate type, overloads 'compareTo' instead of overriding it. + * @kind problem + * @problem.severity error + * @precision medium + * @id java/wrong-compareto-signature + * @tags reliability + * correctness + */ +import java + +private +predicate implementsComparable(RefType t, RefType param) +{ + exists(ParameterizedType pt | t.getASupertype*() = pt and + pt.getSourceDeclaration().hasQualifiedName("java.lang", "Comparable") and + param = pt.getATypeArgument() and + not param instanceof Wildcard and + not param instanceof TypeVariable + ) +} + +private +predicate mostSpecificComparableTypeArgument(RefType t, RefType param) +{ + implementsComparable(t, param) and + not implementsComparable(t, param.getASubtype+()) +} + +private +predicate mostSpecificComparableTypeArgumentOrTypeObject(RefType t, RefType param) +{ + if mostSpecificComparableTypeArgument(t, _) + then mostSpecificComparableTypeArgument(t, param) + else param instanceof TypeObject +} + +private +predicate compareTo(RefType declaring, Method m, RefType param) +{ + m.hasName("compareTo") and m.isPublic() and m.getNumberOfParameters() = 1 and + m.fromSource() and m.getAParamType() = param and declaring = m.getDeclaringType() and + declaring.getASupertype*().getSourceDeclaration().hasQualifiedName("java.lang", "Comparable") +} + +from Method m, Class t, Type actual, Type desired +where + compareTo(t, m, actual) and + mostSpecificComparableTypeArgumentOrTypeObject(t, desired) and + actual != desired and + not compareTo(t, _, desired) and + not actual instanceof TypeVariable +select m, "The parameter of compareTo should have type '" + desired.getName() + + "' when implementing 'Comparable<" + desired.getName() + ">'." diff --git a/java/ql/src/Likely Bugs/Comparison/CovariantEquals.java b/java/ql/src/Likely Bugs/Comparison/CovariantEquals.java new file mode 100644 index 00000000000..9828400a491 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/CovariantEquals.java @@ -0,0 +1,41 @@ +class BadPoint { + int x; + int y; + + BadPoint(int x, int y) { + this.x = x; + this.y = y; + } + + // overloaded equals method -- should be avoided + public boolean equals(BadPoint q) { + return x == q.x && y == q.y; + } +} + +BadPoint p = new BadPoint(1, 2); +Object q = new BadPoint(1, 2); +boolean badEquals = p.equals(q); // evaluates to false + +class GoodPoint { + int x; + int y; + + GoodPoint(int x, int y) { + this.x = x; + this.y = y; + } + + // correctly overrides Object.equals(Object) + public boolean equals(Object obj) { + if (obj != null && getClass() == obj.getClass()) { + GoodPoint q = (GoodPoint)obj; + return x == q.x && y == q.y; + } + return false; + } +} + +GoodPoint r = new GoodPoint(1, 2); +Object s = new GoodPoint(1, 2); +boolean goodEquals = r.equals(s); // evaluates to true diff --git a/java/ql/src/Likely Bugs/Comparison/CovariantEquals.qhelp b/java/ql/src/Likely Bugs/Comparison/CovariantEquals.qhelp new file mode 100644 index 00000000000..a85890af418 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/CovariantEquals.qhelp @@ -0,0 +1,41 @@ + + + + + +

    Classes that define an equals method whose parameter type is not Object +overload the Object.equals method instead of overriding it. This may not be intended.

    + +
    + + +

    To override the Object.equals method, +the parameter of the equals method must have type Object.

    + +
    + + +

    In the following example, the definition of class BadPoint does not override the Object.equals method. +This means that p.equals(q) resolves to the default definition of Object.equals and +returns false. Class GoodPoint correctly overrides Object.equals, +so that r.equals(s) returns true.

    + + + +
    + + + +
  • J. Bloch, Effective Java (second edition), Item 8. Addison-Wesley, 2008.
  • +
  • The Java Language Specification: + Overriding (by Instance Methods), + Overloading.
  • +
  • The Java Tutorials: Overriding and Hiding Methods.
  • + + + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Comparison/CovariantEquals.ql b/java/ql/src/Likely Bugs/Comparison/CovariantEquals.ql new file mode 100644 index 00000000000..926b61d5cd5 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/CovariantEquals.ql @@ -0,0 +1,22 @@ +/** + * @name Overloaded equals + * @description Defining 'Object.equals', where the parameter of 'equals' is not of the + * appropriate type, overloads 'equals' instead of overriding it. + * @kind problem + * @problem.severity error + * @precision medium + * @id java/wrong-equals-signature + * @tags reliability + * correctness + */ +import java + +from RefType t, Method equals +where + t.fromSource() and + equals = t.getAMethod() and + equals.hasName("equals") and + equals.getNumberOfParameters() = 1 and + not t.getAMethod() instanceof EqualsMethod +select equals, + "To override the equals method, the parameter must be of type java.lang.Object." diff --git a/java/ql/src/Likely Bugs/Comparison/DefineEqualsWhenAddingFields.java b/java/ql/src/Likely Bugs/Comparison/DefineEqualsWhenAddingFields.java new file mode 100644 index 00000000000..45df1ee0eb8 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/DefineEqualsWhenAddingFields.java @@ -0,0 +1,30 @@ +public class DefineEqualsWhenAddingFields { + static class Square { + protected int width = 0; + public Square(int width) { + this.width = width; + } + @Override + public boolean equals(Object thatO) { // This method works only for squares. + if(thatO != null && getClass() == thatO.getClass() ) { + Square that = (Square)thatO; + return width == that.width; + } + return false; + } + } + + static class Rectangle extends Square { + private int height = 0; + public Rectangle(int width, int height) { + super(width); + this.height = height; + } + } + + public static void main(String[] args) { + Rectangle r1 = new Rectangle(4, 3); + Rectangle r2 = new Rectangle(4, 5); + System.out.println(r1.equals(r2)); // Outputs 'true' + } +} diff --git a/java/ql/src/Likely Bugs/Comparison/DefineEqualsWhenAddingFields.qhelp b/java/ql/src/Likely Bugs/Comparison/DefineEqualsWhenAddingFields.qhelp new file mode 100644 index 00000000000..948f983d619 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/DefineEqualsWhenAddingFields.qhelp @@ -0,0 +1,50 @@ + + + + + +

    If a class overrides the default implementation of equality defined by +the Object.equals method, and a subclass of that class +declares additional fields to the ones that it inherits, the results of equals +may be wrong, unless that subclass also redefines equals. +

    + +
    + + +

    See if the subclass should provide its own implementation of equals to take into +account the additional fields that it declares.

    + +

    If the subclass cannot provide its own implementation of equals because +the inherited equals method is final, consider replacing inheritance +by composition; instead of class B extending class A, class B +could define a field of type A.

    + +
    + + +

    In the following example, rectangles r1 and r2 are calculated to be equal, +even though they have different dimensions. This is because the class Rectangle does +not override Square.equals, so it uses a test for equality that is only applicable to squares, +not rectangles. (Note that, in practice, the example should also include an implementation of +hashCode.)

    + + + +

    To get the correct result, you must override Square.equals in class Rectangle. +

    + +
    + + + +
  • + Java API Documentation: + Object.equals(). +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Comparison/DefineEqualsWhenAddingFields.ql b/java/ql/src/Likely Bugs/Comparison/DefineEqualsWhenAddingFields.ql new file mode 100644 index 00000000000..1c11f1ab7e3 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/DefineEqualsWhenAddingFields.ql @@ -0,0 +1,93 @@ +/** + * @name Inherited equals() in subclass with added fields + * @description If a class overrides 'Object.equals', and a subclass defines additional fields + * to those it inherits but does not re-define 'equals', the results of 'equals' + * may be wrong. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/inherited-equals-with-added-fields + * @tags reliability + * correctness + */ +import java + +predicate okForEquals(Class c) { + c.getAMethod() instanceof EqualsMethod or + ( + not exists(c.getAField()) and + okForEquals(c.getASupertype()) + ) +} + +/** Holds if method `em` implements a reference equality check. */ +predicate checksReferenceEquality(EqualsMethod em) { + // `java.lang.Object.equals` is the prototypical reference equality implementation. + em.getDeclaringType() instanceof TypeObject or + // Custom reference equality implementations observed in open-source projects. + exists(SingletonBlock blk, EQExpr eq | + blk = em.getBody() and + eq.getAnOperand() instanceof ThisAccess and + eq.getAnOperand().(VarAccess).getVariable() = em.getParameter(0) and + ( + // `{ return (ojb==this); }` + eq = blk.getStmt().(ReturnStmt).getResult().getProperExpr() or + // `{ if (ojb==this) return true; else return false; }` + exists(IfStmt ifStmt | ifStmt = blk.getStmt() | + eq = ifStmt.getCondition().getProperExpr() and + ifStmt.getThen().(ReturnStmt).getResult().(BooleanLiteral).getBooleanValue() = true and + ifStmt.getElse().(ReturnStmt).getResult().(BooleanLiteral).getBooleanValue() = false + ) + ) + ) or + // Check whether `em` delegates to another method checking reference equality. + // More precisely, we check whether the body of `em` is of the form `return super.equals(o);`, + // where `o` is the (only) parameter of `em`, and the invoked method is a reference equality check. + exists(SuperMethodAccess sup | + sup = em.getBody().(SingletonBlock).getStmt().(ReturnStmt).getResult() and + sup.getArgument(0) = em.getParameter(0).getAnAccess() and + checksReferenceEquality(sup.getCallee()) + ) +} + +predicate unsupportedEquals(EqualsMethod em) { + em.getBody().(SingletonBlock).getStmt() instanceof ThrowStmt +} + +predicate overridesDelegateEquals(EqualsMethod em, Class c) { + exists(Method override, Method delegate | + // The `equals` method (declared in the supertype) contains + // a call to a `delegate` method on the same type ... + em.calls(delegate) and + delegate.getDeclaringType() = em.getDeclaringType() and + // ... and the `delegate` method is overridden in the subtype `c` + // by a method that reads at least one added field. + override.getDeclaringType() = c and + exists(Method overridden | overridden.getSourceDeclaration() = delegate | + override.overrides(overridden) + ) and + readsOwnField(override) + ) +} + +predicate readsOwnField(Method m) { + m.reads(m.getDeclaringType().getAField()) +} + +from Class c, InstanceField f, EqualsMethod em +where + not exists(EqualsMethod m | m.getDeclaringType() = c) and + okForEquals(c.getASupertype()) and + exists(Method m | m.getSourceDeclaration() = em | c.inherits(m)) and + exists(em.getBody()) and + not checksReferenceEquality(em) and + not unsupportedEquals(em) and + not overridesDelegateEquals(em, c) and + f.getDeclaringType() = c and + c.fromSource() and + not c instanceof EnumType and + not f.isFinal() +select + c, c.getName() + " inherits $@ but adds $@.", + em.getSourceDeclaration(), em.getDeclaringType().getName() + "." + em.getName(), + f, "the field " + f.getName() diff --git a/java/ql/src/Likely Bugs/Comparison/Equality.qll b/java/ql/src/Likely Bugs/Comparison/Equality.qll new file mode 100644 index 00000000000..898da2bf7e2 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/Equality.qll @@ -0,0 +1,28 @@ +import java +import semmle.code.java.comparison.Comparison + +/** + * An `equals` method that implies the superclass `equals` method is a "finer" equality check. + * + * Importantly, in this case an inherited hash code is still valid, since + * + * subclass equals holds => superclass equals holds => hash codes match + */ +class RefiningEquals extends EqualsMethod { + RefiningEquals() { + // For each return statement `ret` in this method, ... + forall(ReturnStmt ret | ret.getEnclosingCallable() = this | + // ... there is a `super` access that ... + exists(MethodAccess sup, SuperAccess qual | + // ... is of the form `super.something`, but not `A.super.something` ... + qual = sup.getQualifier() and not exists(qual.getQualifier()) and + // ... calls `super.equals` ... + sup.getCallee() instanceof EqualsMethod and + // ... on the (only) parameter of this method ... + sup.getArgument(0).(VarAccess).getVariable() = this.getAParameter() and + // ... and its result is implied by the result of `ret`. + exprImplies(ret.getResult(), true, sup, true) + ) + ) + } +} diff --git a/java/ql/src/Likely Bugs/Comparison/EqualsArray.java b/java/ql/src/Likely Bugs/Comparison/EqualsArray.java new file mode 100644 index 00000000000..401c6806e80 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/EqualsArray.java @@ -0,0 +1,10 @@ +public void arrayExample(){ + String[] array1 = new String[]{"a", "b", "c"}; + String[] array2 = new String[]{"a", "b", "c"}; + + // Reference equality tested: prints 'false' + System.out.println(array1.equals(array2)); + + // Equality of array elements tested: prints 'true' + System.out.println(Arrays.equals(array1, array2)); +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Comparison/EqualsArray.qhelp b/java/ql/src/Likely Bugs/Comparison/EqualsArray.qhelp new file mode 100644 index 00000000000..aee14b2d486 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/EqualsArray.qhelp @@ -0,0 +1,52 @@ + + + + +

    +The equals and hashCode methods on arrays only consider object identity, not +array contents, which is unlikely to be what is intended. +

    + +
    + + +

    To compare the lengths of the arrays and the corresponding pairs of elements in the arrays, use +one of the comparison methods from java.util.Arrays:

    + +
      +
    • The method Arrays.equals performs a shallow comparison. That is, array elements + are compared using equals.
    • +
    • The method Arrays.deepEquals performs a deep comparison, which is appropriate + for comparisons of nested arrays.
    • +
    + +

    +Similarly, Arrays.hashCode and Arrays.deepHashCode can be used to compute +shallow and deep hash codes based on the hash codes of individual array elements. +

    + +
    + + +

    In the following example, the two arrays are first compared using the Object.equals method. +Because this checks only reference equality and the two arrays are different objects, Object.equals +returns false. The two arrays are then compared using the Arrays.equals method. +Because this compares the length and contents of the arrays, Arrays.equals returns +true. +

    + + + +
    + +
  • Java API Documentation: + Arrays.equals, + Arrays.deepEquals, + Object.equals, + Arrays.hashCode, + Arrays.deepHashCode, + Object.hashCode.
  • +
    +
    diff --git a/java/ql/src/Likely Bugs/Comparison/EqualsArray.ql b/java/ql/src/Likely Bugs/Comparison/EqualsArray.ql new file mode 100644 index 00000000000..4461257fb9a --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/EqualsArray.ql @@ -0,0 +1,24 @@ +/** + * @name Equals or hashCode on arrays + * @description The 'equals' and 'hashCode' methods on arrays only consider object identity, not + * array contents, which is unlikely to be what is intended. + * @kind problem + * @problem.severity error + * @precision very-high + * @id java/equals-on-arrays + * @tags reliability + * correctness + */ +import java + +from MethodAccess ma, Array recvtype, Method m +where + recvtype = ma.getQualifier().getType() and + m = ma.getMethod() and + ( + m instanceof HashCodeMethod + or + m instanceof EqualsMethod and + haveIntersection(recvtype, (Array)ma.getArgument(0).getType()) + ) +select ma, "The " + m.getName() + " method on arrays only considers object identity and ignores array contents." diff --git a/java/ql/src/Likely Bugs/Comparison/EqualsUsesInstanceOf.java b/java/ql/src/Likely Bugs/Comparison/EqualsUsesInstanceOf.java new file mode 100644 index 00000000000..045fc55c3ed --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/EqualsUsesInstanceOf.java @@ -0,0 +1,70 @@ +class BadPoint { + int x; + int y; + + BadPoint(int x, int y) { + this.x = x; + this.y = y; + } + + public boolean equals(Object o) { + if(!(o instanceof BadPoint)) + return false; + BadPoint q = (BadPoint)o; + return x == q.x && y == q.y; + } +} + +class BadPointExt extends BadPoint { + String s; + + BadPointExt(int x, int y, String s) { + super(x, y); + this.s = s; + } + + // violates symmetry of equals contract + public boolean equals(Object o) { + if(!(o instanceof BadPointExt)) return false; + BadPointExt q = (BadPointExt)o; + return super.equals(o) && (q.s==null ? s==null : q.s.equals(s)); + } +} + +class GoodPoint { + int x; + int y; + + GoodPoint(int x, int y) { + this.x = x; + this.y = y; + } + + public boolean equals(Object o) { + if (o != null && getClass() == o.getClass()) { + GoodPoint q = (GoodPoint)o; + return x == q.x && y == q.y; + } + return false; + } +} + +class GoodPointExt extends GoodPoint { + String s; + + GoodPointExt(int x, int y, String s) { + super(x, y); + this.s = s; + } + + public boolean equals(Object o) { + if (o != null && getClass() == o.getClass()) { + GoodPointExt q = (GoodPointExt)o; + return super.equals(o) && (q.s==null ? s==null : q.s.equals(s)); + } + return false; + } +} + +BadPoint p = new BadPoint(1, 2); +BadPointExt q = new BadPointExt(1, 2, "info"); diff --git a/java/ql/src/Likely Bugs/Comparison/EqualsUsesInstanceOf.qhelp b/java/ql/src/Likely Bugs/Comparison/EqualsUsesInstanceOf.qhelp new file mode 100644 index 00000000000..fe68c585d85 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/EqualsUsesInstanceOf.qhelp @@ -0,0 +1,84 @@ + + + + + +

    Implementations of equals that use instanceof +to check the type of their argument are likely to lead to non-symmetric definitions of +equals, if they are further overridden in subclasses that add fields and +redefine equals. A definition of the equals method should be +reflexive, symmetric, and transitive, and a violation of the equals contract +may lead to unexpected behavior. +

    + +
    + + +

    Consider using one of the following options:

    + +
      +
    • Check the type of the argument using getClass instead of instanceof.
    • +
    • Declare the class or the equals method final. This prevents the creation of subclasses that + would otherwise violate the equals contract.
    • +
    • Replace inheritance by composition. Instead of a class B extending a class + A, class B can declare a field of type A in addition to + any other fields.
    • +
    + +

    The first option has the disadvantage of violating the substitution principle +of object-oriented languages, which says that an instance of a subclass of A +can be provided whenever an instance of class A is required.

    + +
    + + +

    The first option is illustrated in the following example: +

    + + + +

    +Given the definitions in the example, p.equals(q) returns true +whereas q.equals(p) returns false, +which violates the symmetry requirement of the equals contract. +

    + +

    +Attempting to enforce symmetry by modifying the BadPointExt.equals method to ignore +the field s when its parameter is an instance of type BadPoint +results in violating the transitivity requirement of the equals contract. +

    + +

    +The classes GoodPoint and GoodPointExt avoid violating the equals contract by +using getClass rather than instanceof. +

    + +
    + + + +
  • + J. Bloch, Effective Java (second edition), Items 8 and 16. Addison-Wesley, 2008. +
  • +
  • + Java API Documentation: + Object.equals(). +
  • +
  • + The Java Language Specification: + Type Comparison Operator instanceof. +
  • +
  • + Artima Developer: + How to Write an Equality Method in Java. +
  • +
  • JavaSolutions, April 2002: +Secrets of equals(). +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Comparison/EqualsUsesInstanceOf.ql b/java/ql/src/Likely Bugs/Comparison/EqualsUsesInstanceOf.ql new file mode 100644 index 00000000000..d0cd57a84ad --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/EqualsUsesInstanceOf.ql @@ -0,0 +1,34 @@ +/** + * @name Possible inconsistency due to instanceof in equals + * @description Implementations of 'equals' that use 'instanceof' + * to test the type of the argument and are further overridden in a subclass + * are likely to violate the 'equals' contract. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/instanceof-in-equals + * @tags reliability + * correctness + */ +import java + +predicate instanceofInEquals(EqualsMethod m, InstanceOfExpr e) { + m.fromSource() and + e.getEnclosingCallable() = m and + e.getExpr().(VarAccess).getVariable() = m.getParameter() and + exists(Class instanceofType | + instanceofType = e.getTypeName().getType() and + not instanceofType.isFinal() + ) +} + +from EqualsMethod m, InstanceOfExpr e, EqualsMethod m2 +where + (instanceofInEquals(m, e) or instanceofInEquals(m2, e)) and + not m.getDeclaringType() instanceof TypeObject and + exists(m.getBody()) and + m2.fromSource() and + exists(Method overridden | overridden.getSourceDeclaration() = m | m2.overrides+(overridden)) +select e, "Possible violation of equals contract due to use of instanceof in $@ and/or overriding $@.", + m, m.getDeclaringType().getName() + "." + m.getName(), + m2, m2.getDeclaringType().getName() + "." + m2.getName() diff --git a/java/ql/src/Likely Bugs/Comparison/HashedButNoHash.java b/java/ql/src/Likely Bugs/Comparison/HashedButNoHash.java new file mode 100644 index 00000000000..ed33ed71e62 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/HashedButNoHash.java @@ -0,0 +1,24 @@ +class Point { + int x; + int y; + + Point(int x, int y) { + this.x = x; + this.y = y; + } + + public boolean equals(Object o) { + if (!(o instanceof Point)) return false; + Point q = (Point)o; + return x == q.x && y == q.y; + } + + // Implement hashCode so that equivalent points (with the same values of x and y) have the + // same hash code + public int hashCode() { + int hash = 7; + hash = 31*hash + x; + hash = 31*hash + y; + return hash; + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Comparison/HashedButNoHash.qhelp b/java/ql/src/Likely Bugs/Comparison/HashedButNoHash.qhelp new file mode 100644 index 00000000000..42d6fb70969 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/HashedButNoHash.qhelp @@ -0,0 +1,56 @@ + + + + + +

    Classes that define an equals method but no hashCode method +can lead to unexpected results if instances of those classes are stored in a hashing data structure. +Hashing data structures expect that hash codes fulfill the contract that two objects that +equals considers equal should have the same hash code. This contract is likely to be +violated by such classes.

    + +
    + + +

    Every class that implements a custom equals method should also provide +an implementation of hashCode.

    + +
    + + +

    In the following example, class Point has no implementation of hashCode. +Calling hashCode on two distinct Point objects +with the same coordinates would probably result in different hash codes. +This would violate the contract of the hashCode method, in which case +objects of type Point should not be stored in hashing data structures.

    + + + +

    In the modification of the above example, the implementation of hashCode for class +Point is suitable because the hash code is computed from exactly the same fields that are considered +in the equals method. Therefore, the contract of the hashCode method is fulfilled. +

    + + + +
    + + + +
  • + J. Bloch, Effective Java (second edition), Item 9. Addison-Wesley, 2008. +
  • +
  • + Java API Documentation: + Object.equals, + Object.hashCode. +
  • +
  • + IBM developerWorks: Java theory and practice: Hashing it out. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Comparison/HashedButNoHash.ql b/java/ql/src/Likely Bugs/Comparison/HashedButNoHash.ql new file mode 100644 index 00000000000..7b71da26ae8 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/HashedButNoHash.ql @@ -0,0 +1,56 @@ +/** + * @name Hashed value without hashCode definition + * @description Classes that define an 'equals' method but no 'hashCode' method, and whose instances + * are stored in a hashing data structure, can lead to unexpected results. + * @kind problem + * @problem.severity error + * @precision very-high + * @id java/hashing-without-hashcode + * @tags reliability + * correctness + */ +import java +import Equality + +/** A class that defines an `equals` method but no `hashCode` method. */ +predicate eqNoHash(Class c) { + exists(Method m | m = c.getAMethod() | + m instanceof EqualsMethod and + // If the inherited `equals` is a refining `equals` + // then the superclass hash code is still valid. + not m instanceof RefiningEquals + ) and + not c.getAMethod() instanceof HashCodeMethod and + c.fromSource() +} + +predicate hashingMethod(Method m) { + exists(string name, string names | + names = "add,contains,containsKey,get,put,remove" and + name = names.splitAt(",") and + m.getName() = name + ) +} + +/** Holds if `e` is an expression in which `t` is used in a hashing data structure. */ +predicate usedInHash(RefType t, Expr e) { + exists(RefType s | s.getName().matches("%Hash%") and not s.getSourceDeclaration().getName() = "IdentityHashMap" | + exists(MethodAccess ma | + ma.getQualifier().getType() = s and + ma.getArgument(0).getType() = t and + e = ma and hashingMethod(ma.getMethod()) + ) or + exists(ConstructorCall cc | + cc.getConstructedType() = s and + s.(ParameterizedType).getTypeArgument(0) = t and + cc = e + ) + ) +} + +from RefType t, Expr e +where + usedInHash(t, e) and + eqNoHash(t.getSourceDeclaration()) +select e, "Type '" + t.getName() + "' does not define hashCode(), " + + "but is used in a hashing data-structure." diff --git a/java/ql/src/Likely Bugs/Comparison/HashedButNoHashBad.java b/java/ql/src/Likely Bugs/Comparison/HashedButNoHashBad.java new file mode 100644 index 00000000000..880a7cf838a --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/HashedButNoHashBad.java @@ -0,0 +1,15 @@ +class Point { + int x; + int y; + + Point(int x, int y) { + this.x = x; + this.y = y; + } + + public boolean equals(Object o) { + if (!(o instanceof Point)) return false; + Point q = (Point)o; + return x == q.x && y == q.y; + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Comparison/IncomparableEquals.java b/java/ql/src/Likely Bugs/Comparison/IncomparableEquals.java new file mode 100644 index 00000000000..9ac2db02361 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/IncomparableEquals.java @@ -0,0 +1,10 @@ +String[] anArray = new String[]{"a","b","c"} +String valueToFind = "b"; + +for(int i=0; i + + + + +

    Calls of the form x.equals(y), +where x and y have incomparable types, +should always return false because the runtime types +of x and y will be different. +Two types are incomparable if they are distinct +and do not have a common subtype.

    + +
    + + +

    Ensure that such comparisons use comparable types.

    + +
    + + +

    In the following example, the call to equals on line 5 refers to the whole array by +mistake, instead of a specific element. Therefore, "Value not found" is returned.

    + + + +
    + + + +
  • + Java API Documentation: + Object.equals(). +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Comparison/IncomparableEquals.ql b/java/ql/src/Likely Bugs/Comparison/IncomparableEquals.ql new file mode 100644 index 00000000000..d900e9311b4 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/IncomparableEquals.ql @@ -0,0 +1,63 @@ +/** + * @name Equals on incomparable types + * @description Calls of the form 'x.equals(y)', where the types of 'x' and 'y' are incomparable, + * should always return 'false'. + * @kind problem + * @problem.severity error + * @precision very-high + * @id java/equals-on-unrelated-types + * @tags reliability + * correctness + */ +import java + +/** A call to an `equals` method. */ +class EqualsCall extends MethodAccess { + EqualsCall() { + this.getMethod() instanceof EqualsMethod + } + + /** + * A whitelist of method accesses allowed to perform + * an incomparable-equals call. + */ + predicate whitelisted() { + // Allow tests and assertions to verify that `equals` methods return `false`. + this.getParent*().(MethodAccess).getMethod().getName().matches("assert%") or + this.getEnclosingStmt() instanceof AssertStmt + } + + /** Holds if the callee of this method access is `Object.equals`. */ + predicate invokesObjectEquals() { + this.getMethod().getDeclaringType() instanceof TypeObject + } + + /** Return the (static) type of the argument to `equals`. */ + RefType getArgumentType() { + result = this.getArgument(0).getType() + } +} + +/* + * Find calls to `equals` where the receiver and argument types are incomparable. + * + * We check this in different ways, depending on whether the call invokes + * the trivial `equals` implementation in `Object` or not. + * + * If it does, the two operands have to be of the same runtime type, so if their + * static types have no intersection, the result is guaranteed to be false. + * + * If the `equals` method being invoked is not `Object.equals` but some overridden + * version `A.equals`, we assume that `A.equals` requires the runtime type of its + * operand to be a subtype of `A`. Hence, if `A` and the static type of the argument + * have no intersection, the result is again guaranteed to be false. + */ +from EqualsCall ma, RefType recvtp, RefType argtp +where + not ma.whitelisted() and + (if ma.invokesObjectEquals() + then recvtp = ma.getReceiverType() + else recvtp = ma.getMethod().getDeclaringType()) and + argtp = ma.getArgumentType() and + not haveIntersection(recvtp, argtp) +select ma, "Call to equals() comparing incomparable types " + recvtp.getName() + " and " + argtp.getName() + "." diff --git a/java/ql/src/Likely Bugs/Comparison/InconsistentCompareTo.java b/java/ql/src/Likely Bugs/Comparison/InconsistentCompareTo.java new file mode 100644 index 00000000000..ade47c5d3f8 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/InconsistentCompareTo.java @@ -0,0 +1,10 @@ +public class InconsistentCompareTo implements Comparable { + private int i = 0; + public InconsistentCompareTo(int i) { + this.i = i; + } + + public int compareTo(InconsistentCompareTo rhs) { + return i - rhs.i; + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Comparison/InconsistentCompareTo.qhelp b/java/ql/src/Likely Bugs/Comparison/InconsistentCompareTo.qhelp new file mode 100644 index 00000000000..8bdcc5f7eb1 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/InconsistentCompareTo.qhelp @@ -0,0 +1,53 @@ + + + + + +

    A class that overrides compareTo but not equals +may not implement a natural ordering that is consistent with equals. +

    + +
    + + +

    Although this consistency is not strictly required by the compareTo contract, +usually both methods should be overridden to ensure that they are consistent, that is, that +x.compareTo(y)==0 is true if and only if x.equals(y) +is true, for any non-null x and y.

    + +
    + + +

    In the following example, the class InconsistentCompareTo overrides +compareTo but not equals.

    + + + +

    In the following example, the class InconsistentCompareToFix overrides both +compareTo and equals.

    + + + +

    +If you require a natural ordering that is inconsistent with equals, you should document it clearly. +

    + +
    + + + +
  • + J. Bloch, Effective Java (second edition), Item 12. Addison-Wesley, 2008. +
  • +
  • + Java API Documentation: + Comparable.compareTo, + Comparable, + Object.equals. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Comparison/InconsistentCompareTo.ql b/java/ql/src/Likely Bugs/Comparison/InconsistentCompareTo.ql new file mode 100644 index 00000000000..ef434c20359 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/InconsistentCompareTo.ql @@ -0,0 +1,51 @@ +/** + * @name Inconsistent compareTo + * @description If a class overrides 'compareTo' but not 'equals', it may mean that 'compareTo' + * and 'equals' are inconsistent. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/inconsistent-compareto-and-equals + * @tags reliability + * correctness + */ +import java +import semmle.code.java.frameworks.Lombok + +/** Holds if `t` implements `Comparable` on `typeArg`. */ +predicate implementsComparableOn(RefType t, RefType typeArg) { + exists(RefType cmp | + t.getAnAncestor() = cmp and + cmp.getSourceDeclaration().hasQualifiedName("java.lang", "Comparable") + | + // Either `t` extends `Comparable`, in which case `typeArg` is `T`, ... + typeArg = cmp.(ParameterizedType).getATypeArgument() and not typeArg instanceof Wildcard or + // ... or it extends the raw type `Comparable`, in which case `typeArg` is `Object`. + cmp instanceof RawType and typeArg instanceof TypeObject + ) +} + +class CompareToMethod extends Method { + CompareToMethod() { + this.hasName("compareTo") and + this.isPublic() and + this.getNumberOfParameters() = 1 and + // To implement `Comparable.compareTo`, the parameter must either have type `T` or `Object`. + exists(RefType typeArg, Type firstParamType | + implementsComparableOn(this.getDeclaringType(), typeArg) and + firstParamType = getParameter(0).getType() and + (firstParamType = typeArg or firstParamType instanceof TypeObject) + ) + } +} + +from Class c, CompareToMethod compareToMethod +where + c.fromSource() and + compareToMethod.fromSource() and + not exists(EqualsMethod em | em.getDeclaringType().getSourceDeclaration() = c) and + compareToMethod.getDeclaringType().getSourceDeclaration() = c and + // Exclude classes annotated with relevant Lombok annotations. + not c instanceof LombokEqualsAndHashCodeGeneratedClass +select c, "This class declares $@ but inherits equals; the two could be inconsistent.", + compareToMethod, "compareTo" diff --git a/java/ql/src/Likely Bugs/Comparison/InconsistentCompareToGood.java b/java/ql/src/Likely Bugs/Comparison/InconsistentCompareToGood.java new file mode 100644 index 00000000000..ef40ce9b312 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/InconsistentCompareToGood.java @@ -0,0 +1,14 @@ +public class InconsistentCompareToFix implements Comparable { + private int i = 0; + public InconsistentCompareToFix(int i) { + this.i = i; + } + + public int compareTo(InconsistentCompareToFix rhs) { + return i - rhs.i; + } + + public boolean equals(InconsistentCompareToFix rhs) { + return i == rhs.i; + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Comparison/InconsistentEqualsHashCode.java b/java/ql/src/Likely Bugs/Comparison/InconsistentEqualsHashCode.java new file mode 100644 index 00000000000..1727e731303 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/InconsistentEqualsHashCode.java @@ -0,0 +1,10 @@ +public class InconsistentEqualsHashCode { + private int i = 0; + public InconsistentEqualsHashCode(int i) { + this.i = i; + } + + public int hashCode() { + return i; + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Comparison/InconsistentEqualsHashCode.qhelp b/java/ql/src/Likely Bugs/Comparison/InconsistentEqualsHashCode.qhelp new file mode 100644 index 00000000000..c6bf422cae6 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/InconsistentEqualsHashCode.qhelp @@ -0,0 +1,56 @@ + + + + + +

    A class that overrides only one of equals and hashCode +is likely to violate the contract of the hashCode method. The contract +requires that hashCode gives the same integer result for any two equal objects. +Not enforcing this property may cause unexpected results when storing and +retrieving objects of such a class in a hashing data structure. +

    + +
    + + +

    Usually, both methods should be overridden to ensure that they are consistent. +

    + +
    + + +

    In the following example, the class InconsistentEqualsHashCode overrides +hashCode but not equals.

    + + + +

    In the following example, the class InconsistentEqualsHashCodeFix overrides both +hashCode and equals.

    + + + +
    + + + +
  • + J. Bloch, Effective Java (second edition), Item 9. Addison-Wesley, 2008. +
  • +
  • + Java API Documentation: + Object.equals, + Object.hashCode. +
  • +
  • + IBM developerWorks: Java theory and practice: Hashing it out. +
  • +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Comparison/InconsistentEqualsHashCode.ql b/java/ql/src/Likely Bugs/Comparison/InconsistentEqualsHashCode.ql new file mode 100644 index 00000000000..e740d0547c0 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/InconsistentEqualsHashCode.ql @@ -0,0 +1,29 @@ +/** + * @name Inconsistent equals and hashCode + * @description If a class overrides only one of 'equals' and 'hashCode', it may mean that + * 'equals' and 'hashCode' are inconsistent. + * @kind problem + * @problem.severity error + * @precision very-high + * @id java/inconsistent-equals-and-hashcode + * @tags reliability + * correctness + * external/cwe/cwe-581 + */ +import java +import Equality + +from Class c, string message, Method existingMethod +where + c.fromSource() and + ( + not exists(EqualsMethod e | e.getDeclaringType() = c) and + exists(HashCodeMethod h | h.getDeclaringType() = c and h = existingMethod) and + message = "Class " + c.getName() + " overrides $@ but not equals." + or + not exists(HashCodeMethod h | h.getDeclaringType() = c) and + // If the inherited `equals` is a refining `equals` then the superclass hash code is still valid. + exists(EqualsMethod e | e.getDeclaringType() = c and e = existingMethod and not e instanceof RefiningEquals) and + message = "Class " + c.getName() + " overrides $@ but not hashCode." + ) +select c, message, existingMethod, existingMethod.getName() diff --git a/java/ql/src/Likely Bugs/Comparison/InconsistentEqualsHashCodeGood.java b/java/ql/src/Likely Bugs/Comparison/InconsistentEqualsHashCodeGood.java new file mode 100644 index 00000000000..41bb9ac33d3 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/InconsistentEqualsHashCodeGood.java @@ -0,0 +1,21 @@ +public class InconsistentEqualsHashCodeFix { + private int i = 0; + public InconsistentEqualsHashCodeFix(int i) { + this.i = i; + } + + @Override + public int hashCode() { + return i; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + InconsistentEqualsHashCodeFix that = (InconsistentEqualsHashCodeFix) obj; + return this.i == that.i; + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.java b/java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.java new file mode 100644 index 00000000000..a604dc2a119 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.java @@ -0,0 +1,11 @@ +class A { + // ... + public final boolean equals(Object obj) { + if (!(obj instanceof A)) { + return false; + } + A a = (A)obj; + // ...further checks... + } + // ... +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.qhelp b/java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.qhelp new file mode 100644 index 00000000000..c15382bb6cb --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.qhelp @@ -0,0 +1,59 @@ + + + + + +

    An implementation of equals must be able to handle an argument of any type, to avoid +failing casts. +Therefore, the implementation should inspect the type of its argument to see if the argument can be safely cast to +the class in which the equals method is declared. +

    + +
    + + +

    Usually, an implementation of equals should check +the type of its argument using instanceof, +following the general pattern below.

    + + + +

    Using instanceof in this way has the added benefit that it includes a guard against +null pointer exceptions: if obj is null, the check fails and +false is returned. Therefore, after the check, it is guaranteed that obj is not null, +and its fields can be safely accessed.

    + +

    +Whenever you use instanceof to check the type of the argument, you should declare +the equals method final, so that subclasses are unable to cause a violation +of the symmetry requirement of the equals contract by further overriding equals. +

    + +

    +If you want subclasses to redefine the notion of equality by overriding equals, +use getClass instead of instanceof to check the type of the argument. +However, note that the use of getClass prevents any equality relationship between +instances of a class and its subclasses, even when no additional state is added in a subclass. +

    + +
    + + + +
  • + J. Bloch, Effective Java (second edition), Item 8. Addison-Wesley, 2008. +
  • +
  • + Java API Documentation: + Object.equals(). +
  • +
  • + The Java Language Specification: + Type Comparison Operator instanceof. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.ql b/java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.ql new file mode 100644 index 00000000000..e49fec8f6ee --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.ql @@ -0,0 +1,78 @@ +/** + * @name Equals method does not inspect argument type + * @description An implementation of 'equals' that does not check the type + * of its argument may lead to failing casts. + * @kind problem + * @problem.severity error + * @precision high + * @id java/unchecked-cast-in-equals + * @tags reliability + * correctness + */ +import semmle.code.java.Member +import semmle.code.java.JDK + +/** A cast inside a try-catch block that catches `ClassCastException`. */ +class CheckedCast extends CastExpr { + CheckedCast() { + exists(TryStmt try, RefType cce | + this.getEnclosingStmt().getParent+() = try and + try.getACatchClause().getVariable().getType() = cce and + cce.getQualifiedName() = "java.lang.ClassCastException" + ) + } + + Variable castVariable() { + result.getAnAccess() = this.getExpr() + } +} + +/** An `equals` method with a body of either `return o == this;` + * or `return o == field;` + */ +class ReferenceEquals extends EqualsMethod { + ReferenceEquals() { + exists(Block b, ReturnStmt ret, EQExpr eq | + this.getBody() = b and + b.getStmt(0) = ret and + (ret.getResult() = eq or exists(ParExpr pe | ret.getResult() = pe and pe.getExpr() = eq)) and + eq.getAnOperand() = this.getAParameter().getAnAccess() and + (eq.getAnOperand() instanceof ThisAccess or eq.getAnOperand() instanceof FieldAccess) + ) + } +} + +from EqualsMethod m +where + // The parameter is accessed at least once ... + exists(VarAccess va | va.getVariable() = m.getParameter()) and + // ... but its type is not checked using `instanceof`. + not exists(InstanceOfExpr e | + e.getEnclosingCallable() = m and + e.getExpr().(VarAccess).getVariable() = m.getParameter() + ) and + // Exclude cases that are probably OK. + not exists(MethodAccess ma, Method c | ma.getEnclosingCallable() = m and ma.getMethod() = c | + c.hasName("getClass") or + c.hasName("compareTo") or + c.hasName("equals") or + c.hasName("isInstance") or + c.hasName("reflectionEquals") + or + // If both `this` and the argument are passed to another method, + // or if the argument is passed to a method declared or inherited by `this` type, + // that method may do the right thing. + ma.getAnArgument() = m.getParameter().getAnAccess() and + ( + ma.getAnArgument() instanceof ThisAccess or + exists(Method delegate | delegate.getSourceDeclaration() = ma.getMethod() | + m.getDeclaringType().inherits(delegate) + ) + ) + ) and + not m.getDeclaringType() instanceof Interface and + // Exclude checked casts (casts inside `try`-blocks). + not exists(CheckedCast cast | cast.castVariable() = m.getAParameter()) and + // Exclude `equals` methods that implement reference-equality. + not m instanceof ReferenceEquals +select m, "equals() method does not seem to check argument type." diff --git a/java/ql/src/Likely Bugs/Comparison/NoAssignInBooleanExprs.java b/java/ql/src/Likely Bugs/Comparison/NoAssignInBooleanExprs.java new file mode 100644 index 00000000000..df7444082a6 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/NoAssignInBooleanExprs.java @@ -0,0 +1,30 @@ +public class ScreenView +{ + private static int BUF_SIZE = 1024; + private Screen screen; + + public void notify(Change change) { + boolean restart = false; + if (change.equals(Change.MOVE) + || v.equals(Change.REPAINT) + || (restart = v.equals(Change.RESTART)) // AVOID: Confusing assignment in condition + || v.equals(Change.FLIP)) + { + if (restart) + WindowManager.restart(); + screen.update(); + } + } + + // ... + + public void readConfiguration(InputStream config) { + byte[] buf = new byte[BUF_SIZE]; + int read; + while ((read = config.read(buf)) > 0) { // OK: Assignment whose result is compared to + // another value + // ... + } + // ... + } +} diff --git a/java/ql/src/Likely Bugs/Comparison/NoAssignInBooleanExprs.qhelp b/java/ql/src/Likely Bugs/Comparison/NoAssignInBooleanExprs.qhelp new file mode 100644 index 00000000000..0eee72aa790 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/NoAssignInBooleanExprs.qhelp @@ -0,0 +1,47 @@ + + + + + +

    The assignment operator (=) can easily be confused with the +equality operator (==), and can make a Boolean expression more difficult to understand. +Consequently, assignments in Boolean expressions should be avoided.

    + +

    Some useful idioms are an exception to this rule, such as checking that some bytes have been read +from an input-stream, as shown in the readConfiguration method in the example below. +More precisely, an assignment is allowed in a Boolean expression if the result of the assignment is +compared to another value.

    + +
    + + +

    Consider structuring the condition so that the side-effects are moved outside of the +condition, possibly splitting the condition into several separate tests.

    + +
    + + +

    In the following example, consider the rather confusing assignment to restart in the +notify method. The assignment should be performed outside of the condition instead.

    + + + +
    + + + +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • +
  • +Java Language Specification: +15.21 Equality Operators, +15.26 Assignment Operators. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Comparison/NoAssignInBooleanExprs.ql b/java/ql/src/Likely Bugs/Comparison/NoAssignInBooleanExprs.ql new file mode 100644 index 00000000000..b3e8351a2af --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/NoAssignInBooleanExprs.ql @@ -0,0 +1,38 @@ +/** + * @name Assignment in Boolean expression + * @description Assignments in Boolean conditions can be confused with equality tests and make the + * condition more difficult to understand. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/assignment-in-boolean-expression + * @tags reliability + * readability + * external/cwe/cwe-481 + */ +import semmle.code.java.Expr +import semmle.code.java.Statement + +/** An expression that is used as a condition. */ +class BooleanExpr extends Expr { + BooleanExpr() { + exists(IfStmt s | s.getCondition() = this) or + exists(ForStmt s | s.getCondition() = this) or + exists(WhileStmt s | s.getCondition() = this) or + exists(DoStmt s | s.getCondition() = this) or + exists(ConditionalExpr s | s.getCondition() = this) + } +} + +private predicate assignAndCheck(AssignExpr e) { + exists(BinaryExpr c | e = c.getAChildExpr().getProperExpr() | + c instanceof ComparisonExpr or + c instanceof EqualityTest + ) +} + +from AssignExpr a +where + exists(BooleanExpr expr | expr.getAChildExpr*() = a) and + not assignAndCheck(a) +select a, "Assignment in a boolean expression." diff --git a/java/ql/src/Likely Bugs/Comparison/NoComparisonOnFloats.java b/java/ql/src/Likely Bugs/Comparison/NoComparisonOnFloats.java new file mode 100644 index 00000000000..6ea2fa2e01b --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/NoComparisonOnFloats.java @@ -0,0 +1,7 @@ +class NoComparisonOnFloats +{ + public static void main(String[] args) + { + System.out.println((0.1 + 0.2) == 0.3); + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Comparison/NoComparisonOnFloats.qhelp b/java/ql/src/Likely Bugs/Comparison/NoComparisonOnFloats.qhelp new file mode 100644 index 00000000000..614c995f42f --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/NoComparisonOnFloats.qhelp @@ -0,0 +1,54 @@ + + + + + +

    +Equality tests on floating point values +may lead to unexpected results because of arithmetic imprecision. +For example, the expression 23.42f==23.42 +evaluates to false. +

    + +
    + + +

    Instead of testing for exact equality between floating point values, check that the +difference between the values is within an appropriate error margin.

    + +

    Alternatively, if you do not want any inaccuracy when testing for equality, use one of the +following instead of floating point values:

    +
      +
    • BigDecimal class. This can store decimal values with higher precision.
    • +
    • long type. Because this is an integer type, you must convert any decimal values + to whole values. For example, represent $1.43 as 143 cents.
    • +
    + +
    + +

    In the following example, (0.1 + 0.2) == 0.3 evaluates to false, even +though you would expect it to evaluate to true. This is because of the imprecision of +floating point data types.

    + + +

    In the following improved example, the test for equality is performed by calculating the difference +between the two values, and checking if the difference is within the error margin, EPSILON.

    + + +
    + + + +
  • +J. Bloch, Effective Java (second edition), Item 48. Addison-Wesley, 2008. +
  • +
  • +Numerical Computation Guide: +(What Every Computer Scientist Should Know About Floating-Point Arithmetic). +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Comparison/NoComparisonOnFloats.ql b/java/ql/src/Likely Bugs/Comparison/NoComparisonOnFloats.ql new file mode 100644 index 00000000000..992e6e8c779 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/NoComparisonOnFloats.ql @@ -0,0 +1,68 @@ +/** + * @name Equality test on floating point values + * @description Equality tests on floating point values may lead to unexpected results. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/equality-test-on-floating-point + * @tags reliability + * correctness + */ +import semmle.code.java.Type +import semmle.code.java.Expr + +// Either `float`, `double`, `Float`, or `Double`. +class Floating extends Type { + Floating() { + exists(string s | s = this.getName().toLowerCase() | + s = "float" or + s = "double" + ) + } +} + +predicate trivialLiteral(Literal e) { + e.getValue() = "0.0" or + e.getValue() = "0" or + e.getValue() = "1.0" or + e.getValue() = "1" +} + +predicate definedConstant(Expr e) { + exists(Field f | + f.isStatic() and ( + f.getDeclaringType().hasName("Float") or + f.getDeclaringType().hasName("Double") + ) | + e = f.getAnAccess() + ) +} + +// The contract of `compareTo` would not really allow anything other than `<` or `>` on floats. +predicate comparisonMethod(Method m) { + m.getName() = "compareTo" +} + +// Check for equalities of the form `a.x == b.x` or `a.x == x`, where `x` is assigned to `a.x`, +// which are less interesting but occur often. +predicate similarVarComparison(EqualityTest e) { + exists(Field f | + e.getLeftOperand() = f.getAnAccess() and + e.getRightOperand() = f.getAnAccess() + ) or + exists(Field f, Variable v | + e.getAnOperand() = f.getAnAccess() and + e.getAnOperand() = v.getAnAccess() and + f.getAnAssignedValue() = v.getAnAccess() + ) +} + +from EqualityTest ee +where + ee.getAnOperand().getType() instanceof Floating and + not ee.getAnOperand() instanceof NullLiteral and + not trivialLiteral(ee.getAnOperand()) and + not definedConstant(ee.getAnOperand()) and + not similarVarComparison(ee) and + not comparisonMethod(ee.getEnclosingCallable()) +select ee, "Equality test on floating point values." diff --git a/java/ql/src/Likely Bugs/Comparison/NoComparisonOnFloatsGood.java b/java/ql/src/Likely Bugs/Comparison/NoComparisonOnFloatsGood.java new file mode 100644 index 00000000000..bbe9d323d56 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/NoComparisonOnFloatsGood.java @@ -0,0 +1,8 @@ +class NoComparisonOnFloats +{ + public static void main(String[] args) + { + final double EPSILON = 0.001; + System.out.println(Math.abs((0.1 + 0.2) - 0.3) < EPSILON); + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Comparison/ObjectComparison.qhelp b/java/ql/src/Likely Bugs/Comparison/ObjectComparison.qhelp new file mode 100644 index 00000000000..dcd9282dd1a --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/ObjectComparison.qhelp @@ -0,0 +1,33 @@ + + + + + +

    Reference comparisons (== or !=) +with operands where the static type is Object may not work as intended. +Reference comparisons check if two objects are identical. To check if +two objects are equivalent, use Object.equals instead. +

    + +
    + + +

    Use Object.equals instead of == or !=, and override +the default behavior of the method in a subclass, so that it uses the appropriate notion of +equality. +

    + +
    + + + +
  • + Java API Documentation: + Object.equals(). +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Comparison/ObjectComparison.ql b/java/ql/src/Likely Bugs/Comparison/ObjectComparison.ql new file mode 100644 index 00000000000..7d07d033a75 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/ObjectComparison.ql @@ -0,0 +1,42 @@ +/** + * @name Reference equality test on java.lang.Object + * @description Reference comparisons (== or !=) with operands where the static type is 'Object' may + * not work as intended. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/reference-equality-with-object + * @tags reliability + * correctness + * external/cwe/cwe-595 + */ +import semmle.code.java.Member +import semmle.code.java.JDK + +/** An expression that accesses a field declared `final`. */ +class FinalFieldAccess extends VarAccess { + FinalFieldAccess() { + this.getVariable().(Field).isFinal() + } +} + +class ReferenceEqualityTestOnObject extends EqualityTest { + ReferenceEqualityTestOnObject() { + this.getLeftOperand().getType() instanceof TypeObject and + this.getRightOperand().getType() instanceof TypeObject and + not this.getLeftOperand() instanceof FinalFieldAccess and + not this.getRightOperand() instanceof FinalFieldAccess + } +} + +from ReferenceEqualityTestOnObject scw +where + not exists(Variable left, Variable right, MethodAccess equals | + left = scw.getLeftOperand().(VarAccess).getVariable() and + right = scw.getRightOperand().(VarAccess).getVariable() and + scw.getEnclosingCallable() = equals.getEnclosingCallable() and + equals.getMethod() instanceof EqualsMethod and + equals.getQualifier().(VarAccess).getVariable() = left and + equals.getAnArgument().(VarAccess).getVariable() = right + ) +select scw, "Avoid reference equality for java.lang.Object comparisons." diff --git a/java/ql/src/Likely Bugs/Comparison/RefEqBoxed.java b/java/ql/src/Likely Bugs/Comparison/RefEqBoxed.java new file mode 100644 index 00000000000..617261e80d1 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/RefEqBoxed.java @@ -0,0 +1,3 @@ +boolean realEq(Integer i, Integer j) { + return i.equals(j); +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Comparison/RefEqBoxed.qhelp b/java/ql/src/Likely Bugs/Comparison/RefEqBoxed.qhelp new file mode 100644 index 00000000000..078e4450c4f --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/RefEqBoxed.qhelp @@ -0,0 +1,45 @@ + + + + + +

    Comparing two boxed primitive values using == or +!= compares object identity, which may not be intended.

    + +
    + + +

    Usually, you should compare non-primitive objects, for example boxed primitive values, by using +their equals methods.

    + +
    + + +

    With the following definition, the method call refEq(new Integer(2), new Integer(2)) returns false +because the objects are not identical.

    + + + +

    With the following definition, the method call realEq(new Integer(2), new Integer(2)) +returns true because the objects contain equal values.

    + + + +
    + + + +
  • + J. Bloch and N. Gafter, Java Puzzlers: Traps, Pitfalls, and Corner Cases, Puzzle 32. Addison-Wesley, 2005. +
  • +
  • + Java API Documentation: + Object.equals(), + Integer.equals(). +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Comparison/RefEqBoxed.ql b/java/ql/src/Likely Bugs/Comparison/RefEqBoxed.ql new file mode 100644 index 00000000000..6c053638f82 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/RefEqBoxed.ql @@ -0,0 +1,20 @@ +/** + * @name Reference equality test of boxed types + * @description Comparing two boxed primitive values using the == or != operator + * compares object identity, which may not be intended. + * @kind problem + * @problem.severity error + * @precision very-high + * @id java/reference-equality-of-boxed-types + * @tags reliability + * correctness + * external/cwe/cwe-595 + */ +import java + +from EqualityTest c +where + c.getLeftOperand().getType() instanceof BoxedType and + c.getRightOperand().getType() instanceof BoxedType and + not c.getAnOperand().getType().(RefType).hasQualifiedName("java.lang", "Boolean") +select c, "Suspicious reference comparison of boxed numerical values." diff --git a/java/ql/src/Likely Bugs/Comparison/RefEqBoxedBad.java b/java/ql/src/Likely Bugs/Comparison/RefEqBoxedBad.java new file mode 100644 index 00000000000..29f23d327ad --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/RefEqBoxedBad.java @@ -0,0 +1,3 @@ +boolean refEq(Integer i, Integer j) { + return i == j; +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Comparison/StringComparison.java b/java/ql/src/Likely Bugs/Comparison/StringComparison.java new file mode 100644 index 00000000000..2057187f4d2 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/StringComparison.java @@ -0,0 +1,7 @@ +void printHeader(String headerStyle) { + if (headerStyle == null || headerStyle == "") { + // No header + return; + } + // ... print the header +} diff --git a/java/ql/src/Likely Bugs/Comparison/StringComparison.qhelp b/java/ql/src/Likely Bugs/Comparison/StringComparison.qhelp new file mode 100644 index 00000000000..b0ccb5e6e19 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/StringComparison.qhelp @@ -0,0 +1,58 @@ + + + + + +

    Comparing two String objects using == or +!= compares object identity, which may not be intended. +The same sequence of characters can be represented by two distinct +String objects. +

    + +
    + + +

    To see if two String objects represent +the same sequence of characters, you should usually compare the objects by +using their equals methods. +

    + +
    + + +

    With the following definition, headerStyle is compared +to the empty string using ==. This comparison can yield +false even if headerStyle is the empty string, because +it compares the identity of the two string objects rather than their contents. +For example, if headerStyle was initialized by an XML parser or a JSON +parser, then it might have been created with code like +String.valueOf(buf,start,len). Such code will produce a new string +object every time it is called.

    + + + +

    With the following definition, headerStyle is tested using +the equals method. This version will reliably detect whenever +headerStyle is the empty string.

    + + + +
    + +
  • + Java API Documentation: + String.equals(), + String.intern(). +
  • +
  • + The Java Language Specification: + 15.21.3, + 3.10.5, + 15.28. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Comparison/StringComparison.ql b/java/ql/src/Likely Bugs/Comparison/StringComparison.ql new file mode 100644 index 00000000000..a76a57d9382 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/StringComparison.ql @@ -0,0 +1,64 @@ +/** + * @name Reference equality test on strings + * @description Comparing two strings using the == or != operator + * compares object identity, which may not be intended. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/reference-equality-on-strings + * @tags reliability + * external/cwe/cwe-597 + */ +import java + +/** An expression of type `java.lang.String`. */ +class StringValue extends Expr { + StringValue() { + this.getType() instanceof TypeString + } + + predicate isInterned() { + // A call to `String.intern()`. + exists(Method intern | + intern.getDeclaringType() instanceof TypeString and + intern.hasName("intern") and + this.(MethodAccess).getMethod() = intern + ) + or + // Parenthesized expressions. + this.(ParExpr).getExpr().(StringValue).isInterned() + or + // Ternary conditional operator. + this.(ConditionalExpr).getTrueExpr().(StringValue).isInterned() and + this.(ConditionalExpr).getFalseExpr().(StringValue).isInterned() + or + // Values of type `String` that are compile-time constant expressions (JLS 15.28). + this instanceof CompileTimeConstantExpr + or + // Variables that are only ever assigned an interned `StringValue`. + variableValuesInterned(this.(VarAccess).getVariable()) + or + // Method accesses whose results are all interned. + forex(ReturnStmt rs | rs.getEnclosingCallable() = this.(MethodAccess).getMethod() | + rs.getResult().(StringValue).isInterned() + ) + } +} + +predicate variableValuesInterned(Variable v) { + v.fromSource() and + // All assignments to variables are interned. + forall(StringValue sv | sv = v.getAnAssignedValue() | sv.isInterned()) and + // For parameters, assume they could be non-interned. + not v instanceof Parameter and + // If the string is modified with `+=`, then the new string is not interned + // even if the components are. + not exists(AssignOp append | append.getDest().getProperExpr() = v.getAnAccess()) +} + +from EqualityTest e, StringValue lhs, StringValue rhs +where + e.getLeftOperand() = lhs and + e.getRightOperand() = rhs and + not (lhs.isInterned() and rhs.isInterned()) +select e, "String values compared with " + e.getOp() + "." diff --git a/java/ql/src/Likely Bugs/Comparison/StringComparisonGood.java b/java/ql/src/Likely Bugs/Comparison/StringComparisonGood.java new file mode 100644 index 00000000000..042780e2b77 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/StringComparisonGood.java @@ -0,0 +1,7 @@ +void printHeader(String headerStyle) { + if (headerStyle == null || headerStyle.equals("")) { + // No header + return; + } + // ... print the header +} diff --git a/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.java b/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.java new file mode 100644 index 00000000000..132173924db --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.java @@ -0,0 +1,9 @@ +void method(int x) { + while(x >= 0) { + // do stuff + x--; + } + if (x < 0) { // BAD: always true + // do more stuff + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.qhelp b/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.qhelp new file mode 100644 index 00000000000..624de0a665b --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.qhelp @@ -0,0 +1,38 @@ + + + + +

    The result of certain comparison tests can sometimes be inferred from their +context and the results of other +comparisons. This can be an indication of faulty logic and may result in dead +code or infinite loops if, for example, a loop condition never changes its value. +

    + +
    + +

    Inspect the code to check whether the logic is correct, and consider +simplifying the logical expression. +

    + +
    + +

    In the following example the final test on x will always be +true, and thus the condition is redundant and potentially wrong. +If the "do more stuff" part is intended to always execute after the loop then +the condition should be removed to make this clear. +

    + + + +
    + + +
  • +Java Language Specification: +The if Statement. +
  • + +
    +
    diff --git a/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.ql b/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.ql new file mode 100644 index 00000000000..c9a76381cd3 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.ql @@ -0,0 +1,172 @@ +/** + * @name Useless comparison test + * @description A comparison operation that always evaluates to true or always + * evaluates to false may indicate faulty logic and may result in + * dead code. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id java/constant-comparison + * @tags correctness + * logic + * external/cwe/cwe-570 + * external/cwe/cwe-571 + */ +import semmle.code.java.controlflow.Guards +import semmle.code.java.dataflow.SSA +import semmle.code.java.dataflow.SignAnalysis +import semmle.code.java.dataflow.RangeAnalysis + +/** Holds if `cond` always evaluates to `isTrue`. */ +predicate constCond(BinaryExpr cond, boolean isTrue, Reason reason) { + exists(ComparisonExpr comp, Expr lesser, Expr greater, Bound b, int d1, int d2, Reason r1, Reason r2 | + comp = cond and + lesser = comp.getLesserOperand() and + greater = comp.getGreaterOperand() and + bounded(lesser, b, d1, isTrue, r1) and + bounded(greater, b, d2, isTrue.booleanNot(), r2) and + (reason = r1 or reason = r2) and + (r1 instanceof NoReason and r2 instanceof NoReason or not reason instanceof NoReason) + | + isTrue = true and comp.isStrict() and d1 < d2 or + isTrue = true and not comp.isStrict() and d1 <= d2 or + isTrue = false and comp.isStrict() and d1 >= d2 or + isTrue = false and not comp.isStrict() and d1 > d2 + ) or + exists(EqualityTest eq, Expr lhs, Expr rhs | + eq = cond and + lhs = eq.getLeftOperand() and + rhs = eq.getRightOperand() + | + exists(Bound b, int d1, int d2, boolean upper, Reason r1, Reason r2 | + bounded(lhs, b, d1, upper, r1) and + bounded(rhs, b, d2, upper.booleanNot(), r2) and + isTrue = eq.polarity().booleanNot() and + (reason = r1 or reason = r2) and + (r1 instanceof NoReason and r2 instanceof NoReason or not reason instanceof NoReason) + | + upper = true and d1 < d2 or // lhs <= b + d1 < b + d2 <= rhs + upper = false and d1 > d2 // lhs >= b + d1 > b + d2 >= rhs + ) or + exists(Bound b, int d, Reason r1, Reason r2, Reason r3, Reason r4 | + bounded(lhs, b, d, true, r1) and + bounded(lhs, b, d, false, r2) and + bounded(rhs, b, d, true, r3) and + bounded(rhs, b, d, false, r4) and + isTrue = eq.polarity() + | + (reason = r1 or reason = r2 or reason = r3 or reason = r4) and + (r1 instanceof NoReason and r2 instanceof NoReason and r3 instanceof NoReason and r4 instanceof NoReason or + not reason instanceof NoReason) + ) + ) +} + +/** Holds if `cond` always evaluates to `isTrue`. */ +predicate constCondSimple(BinaryExpr cond, boolean isTrue) { + constCond(cond, isTrue, any(NoReason nr)) +} + +/** Gets a seemingly positive expression that might be negative due to overflow. */ +Expr overFlowCand() { + exists(BinaryExpr bin | + result = bin and + positive(bin.getLeftOperand()) and + positive(bin.getRightOperand()) + | + bin instanceof AddExpr or + bin instanceof MulExpr or + bin instanceof LShiftExpr + ) or + exists(AssignOp op | + result = op and + positive(op.getDest()) and + positive(op.getRhs()) + | + op instanceof AssignAddExpr or + op instanceof AssignMulExpr or + op instanceof AssignLShiftExpr + ) or + exists(AddExpr add, CompileTimeConstantExpr c | + result = add and + add.hasOperands(overFlowCand(), c) and + c.getIntValue() >= 0 + ) or + exists(AssignAddExpr add, CompileTimeConstantExpr c | + result = add and + add.getDest() = overFlowCand() and + add.getRhs() = c and + c.getIntValue() >= 0 + ) or + exists(SsaExplicitUpdate x | result = x.getAUse() and x.getDefiningExpr() = overFlowCand()) or + result.(ParExpr).getExpr() = overFlowCand() or + result.(AssignExpr).getRhs() = overFlowCand() or + result.(LocalVariableDeclExpr).getInit() = overFlowCand() +} + +/** Gets an expression that equals `v` plus a positive value. */ +Expr increaseOfVar(SsaVariable v) { + exists(AssignAddExpr add | + result = add and + positive(add.getDest()) and + add.getRhs() = v.getAUse() + ) or + exists(AddExpr add, Expr e | + result = add and + add.hasOperands(v.getAUse(), e) and + positive(e) + ) or + exists(SsaExplicitUpdate x | result = x.getAUse() and x.getDefiningExpr() = increaseOfVar(v)) or + result.(ParExpr).getExpr() = increaseOfVar(v) or + result.(AssignExpr).getRhs() = increaseOfVar(v) or + result.(LocalVariableDeclExpr).getInit() = increaseOfVar(v) +} + +predicate overFlowTest(ComparisonExpr comp) { + exists(SsaVariable v | + comp.getLesserOperand() = increaseOfVar(v) and + comp.getGreaterOperand() = v.getAUse() + ) or + comp.getLesserOperand() = overFlowCand() and + comp.getGreaterOperand().(IntegerLiteral).getIntValue() = 0 +} + + +/** + * Holds if `test` and `guard` are equality tests of the same integral variable v with constants `c1` and `c2`. + */ +pragma[nomagic] +predicate guardedTest(EqualityTest test, Guard guard, boolean isEq, int i1, int i2) { + exists(SsaVariable v, CompileTimeConstantExpr c1, CompileTimeConstantExpr c2 | + guard.isEquality(v.getAUse(), c1, isEq) and + test.hasOperands(v.getAUse(), c2) and + i1 = c1.getIntValue() and + i2 = c2.getIntValue() and + v.getSourceVariable().getType() instanceof IntegralType + ) +} + +/** + * Holds if `guard` implies that `test` always has the value `testIsTrue`. + */ +predicate uselessEqTest(EqualityTest test, boolean testIsTrue, Guard guard) { + exists(boolean guardIsTrue, boolean guardpolarity, int i | + guardedTest(test, guard, guardpolarity, i, i) and + guard.controls(test.getBasicBlock(), guardIsTrue) and + testIsTrue = guardIsTrue.booleanXor(guardpolarity.booleanXor(test.polarity())) + ) +} + +from BinaryExpr test, boolean testIsTrue, string reason, ExprParent reasonElem +where + ( + if uselessEqTest(test, _, _) then + exists(EqualityTest r | uselessEqTest(test, testIsTrue, r) and reason = ", because of $@" and reasonElem = r) + else if constCondSimple(test, _) then + (constCondSimple(test, testIsTrue) and reason = "" and reasonElem = test) // dummy reason element + else + exists(CondReason r | constCond(test, testIsTrue, r) and reason = ", because of $@" and reasonElem = r.getCond()) + ) and + not overFlowTest(test) and + not exists(AssertStmt assert | assert.getExpr() = test.getParent*()) +select test, "Test is always " + testIsTrue + reason + ".", reasonElem, "this condition" diff --git a/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.qll b/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.qll new file mode 100644 index 00000000000..e5916a9f92b --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.qll @@ -0,0 +1,101 @@ +import java +import semmle.code.java.comparison.Comparison +import semmle.code.java.controlflow.Guards +import semmle.code.java.dataflow.SSA + +/** + * The kind of bound that is known to hold for some variable. + */ +library class BoundKind extends string { + BoundKind() { + this = "=" or + this = "!=" or + this = ">=" or + this = "<=" + } + + predicate isEqual() { + this = "=" + } + + predicate isNotEqual() { + this = "!=" + } + + predicate isLower() { + this = ">=" + } + + predicate isUpper() { + this = "<=" + } + + predicate providesLowerBound() { + isEqual() or isLower() + } + + predicate providesUpperBound() { + isEqual() or isUpper() + } +} + +/** + * The information from `s1` implies that `test` always has the value `testIsTrue`. + */ +predicate uselessTest(ConditionNode s1, BinaryExpr test, boolean testIsTrue) { + exists(ConditionBlock cb, SsaVariable v, BinaryExpr cond, boolean condIsTrue, int k1, int k2, CompileTimeConstantExpr c1, CompileTimeConstantExpr c2 | + s1 = cond and + cb.getCondition() = cond and + cond.hasOperands(v.getAUse(), c1) and c1.getIntValue() = k1 and + test.hasOperands(v.getAUse(), c2) and c2.getIntValue() = k2 and + v.getSourceVariable().getVariable() instanceof LocalScopeVariable and + cb.controls(test.getBasicBlock(), condIsTrue) and + v.getSourceVariable().getType() instanceof IntegralType and + exists(BoundKind boundKind, int bound | + // Simple range analysis. We infer a bound based on `cond` being + // either true (`condIsTrue = true`) or false (`condIsTrue = false`). + exists(EqualityTest condeq | cond = condeq and bound = k1 | + condIsTrue = condeq.polarity() and boundKind.isEqual() + or + condIsTrue = condeq.polarity().booleanNot() and boundKind.isNotEqual() + ) + or + exists(ComparisonExpr comp | comp = cond | + comp.getLesserOperand() = v.getAUse() and + (condIsTrue = true and boundKind.isUpper() and (if comp.isStrict() then bound = k1-1 else bound = k1) + or + condIsTrue = false and boundKind.isLower() and (if comp.isStrict() then bound = k1 else bound = k1+1)) + or + comp.getGreaterOperand() = v.getAUse() and + (condIsTrue = true and boundKind.isLower() and (if comp.isStrict() then bound = k1+1 else bound = k1) + or + condIsTrue = false and boundKind.isUpper() and (if comp.isStrict() then bound = k1 else bound = k1-1)) + ) + | + // Given the bound we check if the `test` is either + // always true (`testIsTrue = true`) or always false (`testIsTrue = false`). + exists(EqualityTest testeq, boolean pol | testeq = test and pol = testeq.polarity() | + ( + boundKind.providesLowerBound() and k2 < bound or + boundKind.providesUpperBound() and bound < k2 or + boundKind.isNotEqual() and k2 = bound + ) and + testIsTrue = pol.booleanNot() + or + boundKind.isEqual() and k2 = bound and testIsTrue = pol + ) + or + exists(ComparisonExpr comp | comp = test | + comp.getLesserOperand() = v.getAUse() and + (boundKind.providesLowerBound() and testIsTrue = false and (k2 < bound or k2 = bound and comp.isStrict()) + or + boundKind.providesUpperBound() and testIsTrue = true and (bound < k2 or bound = k2 and not comp.isStrict())) + or + comp.getGreaterOperand() = v.getAUse() and + (boundKind.providesLowerBound() and testIsTrue = true and (k2 < bound or k2 = bound and not comp.isStrict()) + or + boundKind.providesUpperBound() and testIsTrue = false and (bound < k2 or bound = k2 and comp.isStrict())) + ) + ) + ) +} diff --git a/java/ql/src/Likely Bugs/Comparison/WrongNanComparison.qhelp b/java/ql/src/Likely Bugs/Comparison/WrongNanComparison.qhelp new file mode 100644 index 00000000000..b6be702344c --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/WrongNanComparison.qhelp @@ -0,0 +1,32 @@ + + + + +

    The special floating-point number NaN is defined to be different from all other +floating-point numbers, including itself, when compared using the equality operators, +== and !=. +

    + +
    + +

    To check whether a variable x is NaN use the method isNaN +that is defined on both java.lang.Float and java.lang.Double. +

    + +
    + +

    The expression x == Double.NaN is always false. This expression should be replaced +by Double.isNaN(x), which accurately identifies whether x is equal to Double.NaN. +

    +
    + + +
  • +Java Language Specification: +Numerical Equality Operators == and !=. +
  • + +
    +
    diff --git a/java/ql/src/Likely Bugs/Comparison/WrongNanComparison.ql b/java/ql/src/Likely Bugs/Comparison/WrongNanComparison.ql new file mode 100644 index 00000000000..5feafcec9a8 --- /dev/null +++ b/java/ql/src/Likely Bugs/Comparison/WrongNanComparison.ql @@ -0,0 +1,20 @@ +/** + * @name Wrong NaN comparison + * @description A comparison with 'NaN' using '==' or '!=' will always yield the same result + * and is unlikely to be intended. + * @kind problem + * @problem.severity error + * @precision very-high + * @id java/comparison-with-nan + * @tags correctness + */ +import java + +predicate nanField(Field f) { + f.getDeclaringType() instanceof FloatingPointType and + f.hasName("NaN") +} + +from EqualityTest eq, Field f, string classname +where eq.getAnOperand() = f.getAnAccess() and nanField(f) and f.getDeclaringType().hasName(classname) +select eq, "This comparison will always yield the same result since 'NaN != NaN'. Consider using " + classname + ".isNaN instead" diff --git a/java/ql/src/Likely Bugs/Concurrency/BusyWait.java b/java/ql/src/Likely Bugs/Concurrency/BusyWait.java new file mode 100644 index 00000000000..a350367a432 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/BusyWait.java @@ -0,0 +1,38 @@ +class Message { + public String text = ""; +} + +class Receiver implements Runnable { + private Message message; + public Receiver(Message msg) { + this.message = msg; + } + public void run() { + while(message.text.isEmpty()) { + try { + Thread.sleep(5000); // Sleep while waiting for condition to be satisfied + } catch (InterruptedException e) { } + } + System.out.println("Message Received at " + (System.currentTimeMillis()/1000)); + System.out.println(message.text); + } +} + +class Sender implements Runnable { + private Message message; + public Sender(Message msg) { + this.message = msg; + } + public void run() { + System.out.println("Message sent at " + (System.currentTimeMillis()/1000)); + message.text = "Hello World"; + } +} + +public class BusyWait { + public static void main(String[] args) { + Message msg = new Message(); + new Thread(new Receiver(msg)).start(); + new Thread(new Sender(msg)).start(); + } +} diff --git a/java/ql/src/Likely Bugs/Concurrency/BusyWait.qhelp b/java/ql/src/Likely Bugs/Concurrency/BusyWait.qhelp new file mode 100644 index 00000000000..65d21bcb265 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/BusyWait.qhelp @@ -0,0 +1,62 @@ + + + +

    +Trying to control thread interaction by periodically calling Thread.sleep within a +loop while waiting for a condition to be satisfied is less effective than waiting for a notification. +This is because the waiting thread may either sleep for an unnecessarily long time or wake up too +frequently. This approach may also result in race conditions and, therefore, incorrect code. +

    + +

    +Trying to control thread interaction by repeatedly checking a synchronized data structure without +calling Thread.sleep or waiting for a notification may waste a lot of system resources +and cause noticeable performance problems. +

    + +
    + + +

    See if communication between threads can be improved by using either of the following solutions:

    + +
      +
    • The java.util.concurrent library, preferably
    • +
    • The Object.wait and Object.notifyAll methods
    • +
    + +

    +If following one of these recommendations is not feasible, ensure that race conditions cannot occur +and precise timing is not required for program correctness. +

    + +
    + + +

    In the following example, the Receiver thread sleeps for an unnecessarily long time +(up to five seconds) until it has received the message.

    + + + +

    In the following modification of the above example, the Receiver thread uses the +recommended approach of waiting for a notification that the +message has been sent. This means that the thread can respond immediately instead of sleeping.

    + + + +
    + + + +
  • J. Bloch, Effective Java (second edition), Item 72. Addison-Wesley, 2008.
  • +
  • Java API Documentation: + Object.wait(), + Object.notifyAll(), + java.util.concurrent.
  • +
  • The Java Tutorials: Guarded Blocks, + High Level Concurrency Objects.
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Concurrency/BusyWait.ql b/java/ql/src/Likely Bugs/Concurrency/BusyWait.ql new file mode 100644 index 00000000000..191f14a769a --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/BusyWait.ql @@ -0,0 +1,81 @@ +/** + * @name Busy wait + * @description Calling 'Thread.sleep' to control thread interaction is + * less effective than waiting for a notification and may also + * result in race conditions. Merely synchronizing over shared + * variables in a loop to control thread interaction + * may waste system resources and cause performance problems. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/busy-wait + * @tags reliability + * correctness + * concurrency + */ +import java + +class ReachFromStmt extends Stmt { + ReachFromStmt() { + exists(Method m | m.getBody() = this) or + exists(WhileStmt w | w.getStmt() = this) + } +} + +class SleepMethod extends Method { + SleepMethod() { + this.getName() = "sleep" and + this.getDeclaringType().hasQualifiedName("java.lang","Thread") + } +} + +class SleepMethodAccess extends MethodAccess { + SleepMethodAccess() { + this.getMethod() instanceof SleepMethod + } +} + +class WaitMethod extends Method { + WaitMethod() { + this.getName() = "wait" and + this.getDeclaringType() instanceof TypeObject + } +} + +class ConcurrentMethod extends Method { + ConcurrentMethod() { + this.getDeclaringType().getQualifiedName().matches("java.util.concurrent%") + } +} + +class CommunicationMethod extends Method { + CommunicationMethod() { + this instanceof WaitMethod or + this instanceof ConcurrentMethod + } +} + +predicate callsCommunicationMethod(Method source) { + source instanceof CommunicationMethod + or + exists(MethodAccess a, Method overridingMethod, Method target | + callsCommunicationMethod(overridingMethod) and + overridingMethod.overridesOrInstantiates*(target) and + target = a.getMethod() and + a.getEnclosingCallable() = source + ) +} + +class DangerStmt extends Stmt { + DangerStmt() { + exists(SleepMethodAccess sleep | sleep.getEnclosingStmt() = this) + } +} + +from WhileStmt s, DangerStmt d +where + d.getParent+() = s and + not exists(MethodAccess call | callsCommunicationMethod(call.getMethod()) | + call.getEnclosingStmt().getParent*() = s + ) +select d, "Prefer wait/notify or java.util.concurrent to communicate between threads." diff --git a/java/ql/src/Likely Bugs/Concurrency/BusyWaitGood.java b/java/ql/src/Likely Bugs/Concurrency/BusyWaitGood.java new file mode 100644 index 00000000000..9d5c5b1bac3 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/BusyWaitGood.java @@ -0,0 +1,43 @@ +class Message { + public String text = ""; +} + +class Receiver implements Runnable { + private Message message; + public Receiver(Message msg) { + this.message = msg; + } + public void run() { + synchronized(message) { + while(message.text.isEmpty()) { + try { + message.wait(); // Wait for a notification + } catch (InterruptedException e) { } + } + } + System.out.println("Message Received at " + (System.currentTimeMillis()/1000)); + System.out.println(message.text); + } +} + +class Sender implements Runnable { + private Message message; + public Sender(Message msg) { + this.message = msg; + } + public void run() { + System.out.println("Message sent at " + (System.currentTimeMillis()/1000)); + synchronized(message) { + message.text = "Hello World"; + message.notifyAll(); // Send notification + } + } +} + +public class BusyWait { + public static void main(String[] args) { + Message msg = new Message(); + new Thread(new Receiver(msg)).start(); + new Thread(new Sender(msg)).start(); + } +} diff --git a/java/ql/src/Likely Bugs/Concurrency/CallsToConditionWait.qhelp b/java/ql/src/Likely Bugs/Concurrency/CallsToConditionWait.qhelp new file mode 100644 index 00000000000..148d3dda9af --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/CallsToConditionWait.qhelp @@ -0,0 +1,25 @@ + + + +

    Calling wait on an object of type java.util.concurrent.locks.Condition +may result in unexpected behavior because wait is a method of the Object +class, not the Condition interface itself. Such a call is probably a typographical error: +typing "wait" instead of "await". +

    + +
    + + +

    Instead of Object.wait, use one of the Condition.await methods.

    + +
    + + + +
  • Java API Documentation: java.util.concurrent.Condition.
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Concurrency/CallsToConditionWait.ql b/java/ql/src/Likely Bugs/Concurrency/CallsToConditionWait.ql new file mode 100644 index 00000000000..d92d3e92986 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/CallsToConditionWait.ql @@ -0,0 +1,34 @@ +/** + * @name Wait on condition + * @description Calling 'wait' on a 'Condition' interface may result in unexpected behavior and is + * probably a typographical error. + * @kind problem + * @problem.severity error + * @precision medium + * @id java/wait-on-condition-interface + * @tags reliability + * correctness + * concurrency + * external/cwe/cwe-662 + */ +import java + +class WaitMethod extends Method { + WaitMethod() { + this.hasName("wait") and + this.hasNoParameters() and + this.getDeclaringType().hasQualifiedName("java.lang", "Object") + } +} + +class ConditionInterface extends Interface { + ConditionInterface() { + this.hasQualifiedName("java.util.concurrent.locks", "Condition") + } +} + +from MethodAccess ma, ConditionInterface condition +where + ma.getMethod() instanceof WaitMethod and + ma.getQualifier().getType().(RefType).hasSupertype*(condition) +select ma, "Waiting for a condition should use Condition.await, not Object.wait." diff --git a/java/ql/src/Likely Bugs/Concurrency/CallsToRunnableRun.java b/java/ql/src/Likely Bugs/Concurrency/CallsToRunnableRun.java new file mode 100644 index 00000000000..fb8aaa795ca --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/CallsToRunnableRun.java @@ -0,0 +1,21 @@ +public class ThreadDemo { + public static void main(String args[]) { + NewThread runnable = new NewThread(); + + runnable.run(); // Call to 'run' does not start a separate thread + + System.out.println("Main thread activity."); + } +} + +class NewThread extends Thread { + public void run() { + try { + Thread.sleep(10000); + } + catch (InterruptedException e) { + System.out.println("Child interrupted."); + } + System.out.println("Child thread activity."); + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Concurrency/CallsToRunnableRun.qhelp b/java/ql/src/Likely Bugs/Concurrency/CallsToRunnableRun.qhelp new file mode 100644 index 00000000000..c959b1f8f65 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/CallsToRunnableRun.qhelp @@ -0,0 +1,55 @@ + + + + + +

    A direct call of a Thread object's run method +does not start a separate thread. The method is executed within the current thread. +This is an unusual use because Thread.run() is normally +intended to be called from within a separate thread. +

    + +
    + + +

    To execute Runnable.run from within a separate thread, do one of the +following:

    + +
      +
    • Construct a Thread object using the Runnable object, and call + start on the Thread object.
    • +
    • Define a subclass of a Thread object, and override the definition of its + run method. Then construct an instance of this subclass and call start + on that instance directly.
    • +
    + +
    + + +

    In the following example, the main thread, ThreadDemo, calls the child thread, +NewThread, using run. This causes the child thread to run to +completion before the rest of the main thread is executed, so that "Child thread activity" is +printed before "Main thread activity".

    + + + +

    To enable the two threads to run concurrently, create the child thread and call +start, as shown below. This causes the main thread to +continue while the child thread is waiting, so that "Main thread activity" is printed before +"Child thread activity".

    + + + +
    + + + +
  • + The Java Tutorials: Defining and Starting a Thread. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Concurrency/CallsToRunnableRun.ql b/java/ql/src/Likely Bugs/Concurrency/CallsToRunnableRun.ql new file mode 100644 index 00000000000..de5c596978f --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/CallsToRunnableRun.ql @@ -0,0 +1,28 @@ +/** + * @name Direct call to a run() method + * @description Directly calling a 'Thread' object's 'run' method does not start a separate thread + * but executes the method within the current thread. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/call-to-thread-run + * @tags reliability + * correctness + * concurrency + * external/cwe/cwe-572 + */ +import java + +class RunMethod extends Method{ + RunMethod(){ + this.hasName("run") and + this.hasNoParameters() and + this.getDeclaringType().getASupertype*().hasQualifiedName("java.lang", "Thread") + } +} + +from MethodAccess m, RunMethod run +where + m.getMethod() = run and + not m.getEnclosingCallable() instanceof RunMethod +select m, "Calling 'Thread.run()' rather than 'Thread.start()' will not spawn a new thread." diff --git a/java/ql/src/Likely Bugs/Concurrency/CallsToRunnableRunFixed.java b/java/ql/src/Likely Bugs/Concurrency/CallsToRunnableRunFixed.java new file mode 100644 index 00000000000..706b0583f1f --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/CallsToRunnableRunFixed.java @@ -0,0 +1,9 @@ +public class ThreadDemo { + public static void main(String args[]) { + NewThread runnable = new NewThread(); + + runnable.start(); // Call 'start' method + + System.out.println("Main thread activity."); + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Concurrency/DateFormatThreadUnsafe.java b/java/ql/src/Likely Bugs/Concurrency/DateFormatThreadUnsafe.java new file mode 100644 index 00000000000..36576b00c22 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/DateFormatThreadUnsafe.java @@ -0,0 +1,22 @@ +class DateFormattingThread implements Runnable { + private static DateFormat dateF = new SimpleDateFormat("yyyyMMdd"); // Static field declared + + public void run() { + for(int i=0; i < 10; i++){ + try { + Date d = dateF.parse("20121221"); + System.out.println(d); + } catch (ParseException e) { } + } + } +} + +public class DateFormatThreadUnsafe { + + public static void main(String[] args) { + for(int i=0; i<100; i++){ + new Thread(new DateFormattingThread()).start(); + } + } + +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Concurrency/DateFormatThreadUnsafe.qhelp b/java/ql/src/Likely Bugs/Concurrency/DateFormatThreadUnsafe.qhelp new file mode 100644 index 00000000000..997cd846632 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/DateFormatThreadUnsafe.qhelp @@ -0,0 +1,51 @@ + + + + + +

    Static fields of type java.text.DateFormat or its descendants +should be avoided because the class DateFormat is not thread-safe. +

    + +
    + + +

    Use instance fields instead and synchronize access where necessary.

    + +
    + + +

    In the following example, DateFormattingThread declares a static field dateF +of type DateFormat. When instances of DateFormattingThread are created +and run by DateFormatThreadUnsafe, erroneous results are output because dateF +is shared by all instances of DateFormattingThread.

    + + + +

    In the following modification of the above example, DateFormattingThread declares an +instance field dateF of type DateFormat. When instances of +DateFormattingThread are created and run by DateFormatThreadUnsafeFix, +correct results are output because there is a separate instance of dateF for each +instance of DateFormattingThread.

    + + + +
    + + + +
  • + Java API Documentation: + java.text.DateFormat synchronization. +
  • + + + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Concurrency/DateFormatThreadUnsafe.ql b/java/ql/src/Likely Bugs/Concurrency/DateFormatThreadUnsafe.ql new file mode 100644 index 00000000000..1d4873f77ec --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/DateFormatThreadUnsafe.ql @@ -0,0 +1,24 @@ +/** + * @name Thread-unsafe use of DateFormat + * @description Static fields of type 'DateFormat' (or its descendants) should be avoided + * because the class 'DateFormat' is not thread-safe. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/thread-unsafe-dateformat + * @tags reliability + * correctness + * concurrency + */ +import java + +from Field f, Class dateFormat +where + f.isStatic() and + f.isFinal() and + (f.isPublic() or f.isProtected()) and + dateFormat.hasQualifiedName("java.text", "DateFormat") and + f.getType().(RefType).hasSupertype*(dateFormat) and + exists(MethodAccess m | m.getQualifier().(VarAccess).getVariable() = f) +select f, "Found static field of type " + f.getType().getName() + " in " + + f.getDeclaringType().getName() + "." diff --git a/java/ql/src/Likely Bugs/Concurrency/DateFormatThreadUnsafeGood.java b/java/ql/src/Likely Bugs/Concurrency/DateFormatThreadUnsafeGood.java new file mode 100644 index 00000000000..538c5da83da --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/DateFormatThreadUnsafeGood.java @@ -0,0 +1,22 @@ +class DateFormattingThread implements Runnable { + private DateFormat dateF = new SimpleDateFormat("yyyyMMdd"); // Instance field declared + + public void run() { + for(int i=0; i < 10; i++){ + try { + Date d = dateF.parse("20121221"); + System.out.println(d); + } catch (ParseException e) { } + } + } +} + +public class DateFormatThreadUnsafeFix { + + public static void main(String[] args) { + for(int i=0; i<100; i++){ + new Thread(new DateFormattingThread()).start(); + } + } + +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Concurrency/EmptyRunMethodInThread.java b/java/ql/src/Likely Bugs/Concurrency/EmptyRunMethodInThread.java new file mode 100644 index 00000000000..15df009a10d --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/EmptyRunMethodInThread.java @@ -0,0 +1,8 @@ +class Bad{ + + public void runInThread(){ + Thread thread = new Thread(); + thread.start(); + } + +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Concurrency/EmptyRunMethodInThread.qhelp b/java/ql/src/Likely Bugs/Concurrency/EmptyRunMethodInThread.qhelp new file mode 100644 index 00000000000..13656d73efa --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/EmptyRunMethodInThread.qhelp @@ -0,0 +1,57 @@ + + + + + +

    New threads can be defined using one of the following alternatives:

    + +
      +
    • By extending the Thread class and overriding its run method.
    • +
    • By passing an argument of type Runnable to the constructor of the Thread class.
    • +
    + +

    Thread instances that are defined using another approach are likely to have no effect.

    + +
    + + +

    To avoid empty thread instances, define new threads using one of the following alternatives:

    + +
      +
    • By extending the Thread class and overriding its run method.
    • +
    • By passing an argument of type Runnable to the constructor of the + Thread class.
    • +
    + +
    + + +

    In the following example, class Bad shows the definition of a thread that has no effect.

    + + + +

    In the following example, class GoodWithOverride shows how to extend the +Thread class and override its run method, and class +GoodWithRunnable shows how to pass an argument of type Runnable to the +constructor of the Thread class. + +

    + +
    + + + +
  • + Java API Documentation: + Thread. +
  • +
  • + The Java Tutorials: + Defining and Starting a Thread. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Concurrency/EmptyRunMethodInThread.ql b/java/ql/src/Likely Bugs/Concurrency/EmptyRunMethodInThread.ql new file mode 100644 index 00000000000..25388345b4c --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/EmptyRunMethodInThread.ql @@ -0,0 +1,64 @@ +/** + * @name Useless run() method in thread + * @description Thread instances that neither get an argument of type 'Runnable' passed to their + * constructor nor override the 'Thread.run' method are likely to have no effect. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/empty-run-method-in-thread + * @tags reliability + * correctness + * concurrency + */ +import java + +class ThreadClass extends Class { + ThreadClass() { + this.hasQualifiedName("java.lang", "Thread") + } + + /** + * Any constructor of `java.lang.Thread` _without_ a parameter of type `Runnable`; + * these require overriding the `Thread.run()` method in order to do anything useful. + */ + Constructor getAConstructorWithoutRunnableParam() { + result = this.getAConstructor() and + ( + result.getNumberOfParameters() = 0 or + ( + result.getNumberOfParameters() = 1 and + result.getParameter(0).getType().(RefType).hasQualifiedName("java.lang", "String") + ) or + ( + result.getNumberOfParameters() = 2 and + result.getParameter(0).getType().(RefType).hasQualifiedName("java.lang", "ThreadGroup") and + result.getParameter(1).getType().(RefType).hasQualifiedName("java.lang", "String") + ) + ) + } + + /** + * Any constructor of `java.lang.Thread` _with_ a parameter of type `Runnable`; + * these ensure that the `Thread.run()` method calls the `Runnable.run()` method. + */ + Constructor getAConstructorWithRunnableParam() { + result = this.getAConstructor() and + not exists(Constructor c | c = result | result = this.getAConstructorWithoutRunnableParam()) + } +} + +from ClassInstanceExpr cie, ThreadClass thread, Class emptythread +where + emptythread.hasSupertype*(thread) and + not exists(Class middle, Method run | + run.hasName("run") and + run.isPublic() and + not run.isAbstract() and + run.hasNoParameters() and + middle.getAMethod() = run and + middle.hasSupertype+(thread) and + emptythread.hasSupertype*(middle) + ) and + not cie.getConstructor().callsConstructor*(thread.getAConstructorWithRunnableParam()) and + cie.getType().(RefType).getSourceDeclaration() = emptythread +select cie, "Thread " + emptythread.getName() + " has a useless empty run() inherited from java.lang.Thread." diff --git a/java/ql/src/Likely Bugs/Concurrency/EmptyRunMethodInThreadGood.java b/java/ql/src/Likely Bugs/Concurrency/EmptyRunMethodInThreadGood.java new file mode 100644 index 00000000000..f43bed5d856 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/EmptyRunMethodInThreadGood.java @@ -0,0 +1,29 @@ +class GoodWithOverride{ + + public void runInThread(){ + Thread thread = new Thread(){ + @Override + public void run(){ + System.out.println("Doing something"); + } + }; + thread.start; + } + +} + +class GoodWithRunnable{ + + public void runInThread(){ + Runnable thingToRun = new Runnable(){ + @Override + public void run(){ + System.out.println("Doing something"); + } + }; + + Thread thread = new Thread(thingToRun()); + thread.start(); + } + +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Concurrency/FutileSynchOnField.java b/java/ql/src/Likely Bugs/Concurrency/FutileSynchOnField.java new file mode 100644 index 00000000000..c66dec00119 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/FutileSynchOnField.java @@ -0,0 +1,10 @@ +public class A { + private Object field; + + public void setField(Object o){ + synchronized (field){ // BAD: synchronize on the field to be updated + field = o; + // ... more code ... + } + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Concurrency/FutileSynchOnField.qhelp b/java/ql/src/Likely Bugs/Concurrency/FutileSynchOnField.qhelp new file mode 100644 index 00000000000..6de0a40237f --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/FutileSynchOnField.qhelp @@ -0,0 +1,51 @@ + + + + + +

    +A block of code that synchronizes on a field and updates that field while the +lock is held is unlikely to provide the desired thread safety. Such a synchronized +block does not prevent multiple unsynchronized assignments to that field because it +obtains a lock on the object stored in the field rather than the field itself. +

    + +
    + + +

    Instead of synchronizing on the field itself, consider synchronizing on a separate lock object +when you want to avoid simultaneous updates to the field. +You can do this by declaring a synchronized method and using it for any field updates. +

    + +
    + + +

    In the following example, in class A, synchronization takes place on the field that is updated in the body of the setField method.

    + + + +

    In class B, the recommended approach is shown, where synchronization takes place on a separate lock +object.

    + + + +
    + + + +
  • + The Java Language Specification: + The synchronized Statement, + synchronized Methods. +
  • +
  • + The Java Tutorials: + Lock Objects. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Concurrency/FutileSynchOnField.ql b/java/ql/src/Likely Bugs/Concurrency/FutileSynchOnField.ql new file mode 100644 index 00000000000..5e575c38e10 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/FutileSynchOnField.ql @@ -0,0 +1,33 @@ +/** + * @name Futile synchronization on field + * @description Synchronizing on a field and updating that field while the lock is held is unlikely + * to provide the desired thread safety. + * @kind problem + * @problem.severity error + * @precision medium + * @id java/unsafe-sync-on-field + * @tags reliability + * correctness + * concurrency + * language-features + * external/cwe/cwe-662 + */ +import java + +private +Field synchField(SynchronizedStmt s) { + result = s.getExpr().(VarAccess).getVariable() +} + +private +Field assignmentToField(Assignment a) { + result = a.getDest().(VarAccess).getVariable() +} + +from SynchronizedStmt s, Field f, Assignment a +where + synchField(s) = f and + assignmentToField(a) = f and + a.getEnclosingStmt().getParent*() = s +select a, "Synchronization on field $@ in futile attempt to guard that field.", + f, f.getDeclaringType().getName() + "." + f.getName() diff --git a/java/ql/src/Likely Bugs/Concurrency/FutileSynchOnFieldGood.java b/java/ql/src/Likely Bugs/Concurrency/FutileSynchOnFieldGood.java new file mode 100644 index 00000000000..d4abac0789a --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/FutileSynchOnFieldGood.java @@ -0,0 +1,11 @@ +public class B { + private final Object lock = new Object(); + private Object field; + + public void setField(Object o){ + synchronized (lock){ // GOOD: synchronize on a separate lock object + field = o; + // ... more code ... + } + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Concurrency/InconsistentAccess.java b/java/ql/src/Likely Bugs/Concurrency/InconsistentAccess.java new file mode 100644 index 00000000000..849fbe912aa --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/InconsistentAccess.java @@ -0,0 +1,16 @@ +class MultiThreadCounter { + public int counter = 0; + + public void modifyCounter() { + synchronized(this) { + counter--; + } + synchronized(this) { + counter--; + } + synchronized(this) { + counter--; + } + counter = counter + 3; // No synchronization + } +} diff --git a/java/ql/src/Likely Bugs/Concurrency/InconsistentAccess.qhelp b/java/ql/src/Likely Bugs/Concurrency/InconsistentAccess.qhelp new file mode 100644 index 00000000000..44ea3629259 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/InconsistentAccess.qhelp @@ -0,0 +1,42 @@ + + + + + +

    If a field is mostly accessed in a synchronized context, but occasionally accessed in a non-synchronized way, +the non-synchronized accesses may lead to race conditions. +

    + +
    + + +

    Ensure that the non-synchronized field accesses are made synchronized, if required.

    + +
    + + +

    In the following example, counter is accessed in a synchronized way in +all but one cases. If modifyCounter is called by a large number of threads that +are running concurrently, the value of counter at the end of each call may not be zero. +This is because the non-synchronized statement could be interleaved with updates to the counter that +are performed by the other threads.

    + + + +

    To correct this, the last statement of modifyCounter should be enclosed in a +synchronized statement.

    + +
    + + + +
  • + The Java Language Specification: + Synchronization. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Concurrency/InconsistentAccess.ql b/java/ql/src/Likely Bugs/Concurrency/InconsistentAccess.ql new file mode 100644 index 00000000000..83c46e623ad --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/InconsistentAccess.ql @@ -0,0 +1,75 @@ +/** + * @name Inconsistent synchronization for field + * @description If a field is mostly accessed in a synchronized context, but occasionally accessed + * in a non-synchronized way, the non-synchronized accesses may lead to race + * conditions. + * @kind problem + * @problem.severity error + * @precision low + * @id java/inconsistent-field-synchronization + * @tags reliability + * correctness + * concurrency + * language-features + * external/cwe/cwe-662 + * statistical + * non-attributable + */ +import java + +predicate withinInitializer(Expr e) { + e.getEnclosingCallable().hasName("") or + e.getEnclosingCallable() instanceof Constructor +} + +predicate locallySynchronized(MethodAccess ma) { + ma.getEnclosingStmt().getParent+() instanceof SynchronizedStmt +} + +predicate hasUnsynchronizedCall(Method m) { + (m.isPublic() and not m.isSynchronized()) + or + exists(MethodAccess ma, Method caller | ma.getMethod() = m and caller = ma.getEnclosingCallable() | + hasUnsynchronizedCall(caller) and + not caller.isSynchronized() and + not locallySynchronized(ma) + ) +} + +predicate withinLocalSynchronization(Expr e) { + e.getEnclosingCallable().isSynchronized() or + e.getEnclosingStmt().getParent+() instanceof SynchronizedStmt +} + +class MyField extends Field { + MyField() { + this.fromSource() and + not this.isFinal() and + not this.isVolatile() and + not this.getDeclaringType() instanceof EnumType + } + + int getNumSynchedAccesses() { + result = count(Expr synched | synched = this.getAnAccess() and withinLocalSynchronization(synched)) + } + + int getNumAccesses() { + result = count(this.getAnAccess()) + } + + float getPercentSynchedAccesses() { + result = (float)this.getNumSynchedAccesses() / this.getNumAccesses() + } +} + +from MyField f, Expr e, int percent +where + e = f.getAnAccess() and + not withinInitializer(e) and + not withinLocalSynchronization(e) and + hasUnsynchronizedCall(e.getEnclosingCallable()) and + f.getNumSynchedAccesses() > 0 and + percent = (f.getPercentSynchedAccesses() * 100).floor() and + percent > 80 +select e, "Unsynchronized access to $@, but " + percent.toString() + "% of accesses to this field are synchronized.", + f, f.getDeclaringType().getName() + "." + f.getName() diff --git a/java/ql/src/Likely Bugs/Concurrency/LazyInitStaticField.java b/java/ql/src/Likely Bugs/Concurrency/LazyInitStaticField.java new file mode 100644 index 00000000000..df55d4b24f8 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/LazyInitStaticField.java @@ -0,0 +1,9 @@ +class Singleton { + private static Resource resource; + + public Resource getResource() { + if(resource == null) + resource = new Resource(); // Lazily initialize "resource" + return resource; + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Concurrency/LazyInitStaticField.qhelp b/java/ql/src/Likely Bugs/Concurrency/LazyInitStaticField.qhelp new file mode 100644 index 00000000000..8ea8b951924 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/LazyInitStaticField.qhelp @@ -0,0 +1,50 @@ + + + + + +

    The tactic of initializing a static field the first time it is used, known as "lazy initialization", can be problematic +in a multi-threaded context when used without proper synchronization. If a separate thread starts executing before the field is initialized, +the thread may see an incompletely initialized object. +

    + +
    + + +

    If lazy initialization is desirable for performance reasons, the best solution is usually to +declare the enclosing method synchronized. Otherwise, avoid lazy initialization and +initialize static fields using static initializers. A third possibility is to declare the field +volatile and use the double-checked locking idiom as explained in the article +referenced below. As the article points out, it is crucial to declare the field +volatile: double-checked locking by itself is not correct under the Java +memory model. +

    + +
    + + +

    In the following example, the static field resource is initialized without +synchronization.

    + + + +

    In the following modification of the above example, Singleton uses the recommended +approach of using a static initializer to initialize resource.

    + + + +
    + + + +
  • University of Maryland Department of Computer Science: The "Double-Checked Locking is Broken" Declaration.
  • + + + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Concurrency/LazyInitStaticField.ql b/java/ql/src/Likely Bugs/Concurrency/LazyInitStaticField.ql new file mode 100644 index 00000000000..7b3000789ff --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/LazyInitStaticField.ql @@ -0,0 +1,113 @@ +/** + * @name Incorrect lazy initialization of a static field + * @description Initializing a static field without synchronization can be problematic + * in a multi-threaded context. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/lazy-initialization + * @tags reliability + * correctness + * concurrency + * external/cwe/cwe-543 + * external/cwe/cwe-609 + */ +import java + +/** A comparison (using `==`) with `null`. */ +class NullEQExpr extends EQExpr { + NullEQExpr() { + exists(NullLiteral l | l.getParent() = this) + } +} + +/** An assignment to a static field. */ +class StaticFieldInit extends AssignExpr { + StaticFieldInit() { + exists(Field f | f.isStatic() | + f.getAnAccess() = this.getDest() + ) + } + + Field getField() { + result.getAnAccess() = this.getDest() + } + + IfStmt getAnEnclosingNullCheck() { + result.getThen().getAChild*() = this.getEnclosingStmt() and + result.getCondition().(NullEQExpr).getAChildExpr() = getField().getAnAccess() + } + + IfStmt getNearestNullCheck() { + result = getAnEnclosingNullCheck() and + not result.getAChild+() = getAnEnclosingNullCheck() + } +} + +/** A field that is a candidate for a "lock object". */ +class LockObjectField extends Field { + LockObjectField() { + this.isStatic() and + forex(Callable init | init = this.getAnAssignedValue().getEnclosingCallable() | + init instanceof StaticInitializer + ) + } +} + +/** A synchronized statement on a class literal. */ +class ValidSynchStmt extends Stmt { + ValidSynchStmt() { + // It's OK to lock the enclosing class. + this.(SynchronizedStmt).getExpr().(TypeLiteral).getTypeName().getType() = this.getEnclosingCallable().getDeclaringType() or + // It's OK to lock on a "lock object field". + this.(SynchronizedStmt).getExpr().(FieldRead).getField() instanceof LockObjectField or + // Locking via `ReentrantLock` lock object instead of synchronized statement. + exists(TryStmt try, LockObjectField lockField | + this = try.getBlock() and + lockField.getType().(RefType).hasQualifiedName("java.util.concurrent.locks", "ReentrantLock") and + exists(MethodAccess lockAction | + lockAction.getQualifier() = lockField.getAnAccess() and + lockAction.getMethod().getName() = "lock" and + dominates(lockAction, this) + ) and + exists(MethodAccess unlockAction | + unlockAction.getQualifier() = lockField.getAnAccess() and + unlockAction.getMethod().getName() = "unlock" and + postDominates(unlockAction, this) + ) + ) + } +} + +/** A static method. */ +class StaticMethod extends Method { + StaticMethod() { this.isStatic() } + + predicate bodyIsSynchronized() { + this.isSynchronized() or + this.getBody().getAChild() instanceof ValidSynchStmt + } +} + +from StaticMethod method, IfStmt i, StaticFieldInit init, string message +where + i = init.getNearestNullCheck() and + method = i.getEnclosingCallable() and + not method.bodyIsSynchronized() and + not method instanceof StaticInitializer and + // There must be an unsynchronized read. + exists(IfStmt unsyncNullCheck | unsyncNullCheck = init.getAnEnclosingNullCheck() | + not unsyncNullCheck.getParent+() instanceof ValidSynchStmt + ) and + if (i.getParent+() instanceof ValidSynchStmt) then ( + not init.getField().isVolatile() and + message = "The field must be volatile." + ) else ( + if (i.getParent+() instanceof SynchronizedStmt) then ( + message = "Bad synchronization." + ) else ( + message = "Missing synchronization." + ) + ) +select init, "Incorrect lazy initialization of static field $@: " + message, + init.getField() as f, f.getName() diff --git a/java/ql/src/Likely Bugs/Concurrency/LazyInitStaticFieldGood.java b/java/ql/src/Likely Bugs/Concurrency/LazyInitStaticFieldGood.java new file mode 100644 index 00000000000..f655738e5fd --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/LazyInitStaticFieldGood.java @@ -0,0 +1,11 @@ +class Singleton { + private static Resource resource; + + static { + resource = new Resource(); // Initialize "resource" only once + } + + public Resource getResource() { + return resource; + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Concurrency/NonSynchronizedOverride.qhelp b/java/ql/src/Likely Bugs/Concurrency/NonSynchronizedOverride.qhelp new file mode 100644 index 00000000000..5e1cf9362d3 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/NonSynchronizedOverride.qhelp @@ -0,0 +1,36 @@ + + + + + +

    If a synchronized method is overridden in a subclass, +the compiler does not require the overriding method to be +synchronized. However, if the overriding method is not synchronized, the thread-safety of the +subclass may be broken. +

    + +
    + + +

    Ensure that the overriding +method is synchronized, if necessary. +

    + +
    + + + +
  • + The Java Language Specification: + Synchronization. +
  • +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Concurrency/NonSynchronizedOverride.ql b/java/ql/src/Likely Bugs/Concurrency/NonSynchronizedOverride.ql new file mode 100644 index 00000000000..ffbe8f34429 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/NonSynchronizedOverride.ql @@ -0,0 +1,59 @@ +/** + * @name Non-synchronized override of synchronized method + * @description If a synchronized method is overridden in a subclass, and the overriding method is + * not synchronized, the thread-safety of the subclass may be broken. + * @kind problem + * @problem.severity error + * @precision very-high + * @id java/non-sync-override + * @tags reliability + * correctness + * concurrency + * language-features + * external/cwe/cwe-820 + */ + +import java + +/** + * Check whether expression `e` is a call to method `target` of the form + * `super.m(x, y, z)`, possibly wrapped in one or more casts and/or parentheses. + */ +predicate delegatingSuperCall(Expr e, Method target) { + exists(MethodAccess call | call = e | + call.getQualifier() instanceof SuperAccess and + call.getCallee() = target and + forall(Expr arg | arg = call.getAnArgument() | arg instanceof VarAccess) + ) or + delegatingSuperCall(e.(CastExpr).getExpr(), target) or + delegatingSuperCall(e.(ParExpr).getExpr(), target) +} + +/** + * Check whether method `sub` is a trivial override of method `sup` that simply + * delegates to `sup`. + */ +predicate delegatingOverride(Method sub, Method sup) { + exists(Stmt stmt | + // The body of `sub` consists of a single statement... + stmt = sub.getBody().(SingletonBlock).getStmt() and + ( + // ...that is either a delegating call to `sup` (with a possible cast)... + delegatingSuperCall(stmt.(ExprStmt).getExpr(), sup) or + // ...or a `return` statement containing such a call. + delegatingSuperCall(stmt.(ReturnStmt).getResult(), sup) + ) + ) +} + +from Method sub, Method sup, Class supSrc +where + sub.overrides(sup) and + sub.fromSource() and + sup.isSynchronized() and + not sub.isSynchronized() and + not delegatingOverride(sub, sup) and + supSrc = sup.getDeclaringType().getSourceDeclaration() +select sub, + "Method '" + sub.getName() + "' overrides a synchronized method in $@ but is not synchronized.", + supSrc, supSrc.getQualifiedName() diff --git a/java/ql/src/Likely Bugs/Concurrency/NotifyNotNotifyAll.java b/java/ql/src/Likely Bugs/Concurrency/NotifyNotNotifyAll.java new file mode 100644 index 00000000000..bc10651851d --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/NotifyNotNotifyAll.java @@ -0,0 +1,32 @@ +class ProducerConsumer { + private static final int MAX_SIZE=3; + private List buf = new ArrayList(); + + public synchronized void produce(Object o) { + while (buf.size()==MAX_SIZE) { + try { + wait(); + } + catch (InterruptedException e) { + ... + } + } + buf.add(o); + notify(); // 'notify' is used + } + + public synchronized Object consume() { + + while (buf.size()==0) { + try { + wait(); + } + catch (InterruptedException e) { + ... + } + } + Object o = buf.remove(0); + notify(); // 'notify' is used + return o; + } +} diff --git a/java/ql/src/Likely Bugs/Concurrency/NotifyNotNotifyAll.qhelp b/java/ql/src/Likely Bugs/Concurrency/NotifyNotNotifyAll.qhelp new file mode 100644 index 00000000000..76270d861c7 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/NotifyNotNotifyAll.qhelp @@ -0,0 +1,54 @@ + + + + + +

    Calls to the notify method rather than notifyAll +may fail to wake up the correct thread if an object's monitor (intrinsic lock) is used for +multiple conditions. notify only wakes up a single arbitrary thread that +is waiting on the object's monitor, whereas notifyAll +wakes up all such threads. +

    + +
    + + +

    Ensure that the call to notify +instead of notifyAll is a correct and desirable optimization. +If not, call notifyAll instead. +

    + +
    + + +

    In the following example, the methods produce and consume both use +notify to tell any waiting threads that an object has been added or removed from the buffer. +However, this means that only one thread is notified. The woken-up thread might not be able to proceed +due to its condition being false, immediately going back to the waiting state. As a result no progress is made.

    + + + +

    When using notifyAll instead of notify, all threads are notified, +and if there are any threads that could proceed, we can be sure that at least one of them will do so. +

    + +
    + + + +
  • + J. Bloch. + Effective Java (second edition), p. 277. + Addison-Wesley, 2008. +
  • +
  • + Java API Documentation: + notify(), + notifyAll(). +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Concurrency/NotifyNotNotifyAll.ql b/java/ql/src/Likely Bugs/Concurrency/NotifyNotNotifyAll.ql new file mode 100644 index 00000000000..1cd857bf175 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/NotifyNotNotifyAll.ql @@ -0,0 +1,30 @@ +/** + * @name notify instead of notifyAll + * @description Calling 'notify' instead of 'notifyAll' may fail to wake up the correct thread and + * cannot wake up multiple threads. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/notify-instead-of-notify-all + * @tags reliability + * correctness + * concurrency + * external/cwe/cwe-662 + */ + +import java + +class InvokeInterfaceOrVirtualMethodAccess extends MethodAccess { + InvokeInterfaceOrVirtualMethodAccess() { + this.getMethod().getDeclaringType() instanceof Interface or + not this.hasQualifier() or + not this.getQualifier() instanceof SuperAccess + } +} + +from InvokeInterfaceOrVirtualMethodAccess ma, Method m +where + ma.getMethod() = m and + m.hasName("notify") and + m.hasNoParameters() +select ma, "Using notify rather than notifyAll." diff --git a/java/ql/src/Likely Bugs/Concurrency/NotifyWithoutSynch.java b/java/ql/src/Likely Bugs/Concurrency/NotifyWithoutSynch.java new file mode 100644 index 00000000000..a816c465d1c --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/NotifyWithoutSynch.java @@ -0,0 +1,31 @@ +class ProducerConsumer { + private static final int MAX_SIZE=3; + private List buf = new ArrayList(); + + public synchronized void produce(Object o) { + while (buf.size()==MAX_SIZE) { + try { + wait(); + } + catch (InterruptedException e) { + ... + } + } + buf.add(o); + notifyAll(); + } + + public Object consume() { + while (buf.size()==0) { + try { + wait(); + } + catch (InterruptedException e) { + ... + } + } + Object o = buf.remove(0); + notifyAll(); + return o; + } +} diff --git a/java/ql/src/Likely Bugs/Concurrency/NotifyWithoutSynch.qhelp b/java/ql/src/Likely Bugs/Concurrency/NotifyWithoutSynch.qhelp new file mode 100644 index 00000000000..ee83e53b000 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/NotifyWithoutSynch.qhelp @@ -0,0 +1,54 @@ + + + + + +

    The methods notify, notifyAll, and wait + should only be called by a thread that is the owner of the object's monitor + (intrinsic lock). In other words, the methods should only be called from within + a synchronized statement or method. Otherwise the method call will throw + IllegalMonitorStateException. +

    + +
    + + +

    Ensure that calls to notify, notifyAll, + or wait are called from within a synchronized statement or method. +

    + +
    + + +

    In the following example, the methods produce and consume use +wait and notifyAll to communicate. However, the consume + method is not synchronized, so the calls to wait and notifyAll will + always throw an exception.

    + + + +

    To fix this example, add the synchronized keyword to the + declaration of the consume method.

    + +
    + + + +
  • + J. Bloch. + Effective Java (second edition), p. 276. + Addison-Wesley, 2008. +
  • +
  • + Java API Documentation: + notify(), + notifyAll(), + wait(), + wait(long). +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Concurrency/NotifyWithoutSynch.ql b/java/ql/src/Likely Bugs/Concurrency/NotifyWithoutSynch.ql new file mode 100644 index 00000000000..aab5f55798f --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/NotifyWithoutSynch.ql @@ -0,0 +1,139 @@ +/** + * @name Notify on unlocked object + * @description Calling 'wait', 'notify', or 'notifyAll' on an object which has not + * been locked (with a synchronized method or statement) will throw. + * @kind problem + * @problem.severity error + * @precision low + * @id java/notify-without-sync + * @tags correctness + * concurrency + * language-features + */ +import java + +/** The set of methods to test: `wait`, `notify`, `notifyAll`. */ +class MethodRequiresSynch extends Method { + MethodRequiresSynch() { + ( + this.hasName("wait") or + this.hasName("notify") or + this.hasName("notifyAll") + ) + and + this.getDeclaringType().hasQualifiedName("java.lang","Object") + } +} + +/** + * Auxiliary predicate for `synchronizedThisAccess`. It holds if `c` + * has a `synchronized` modifier or if `c` is private and all of + * its call sites are from an appropriate synchronization context. + * + * This means that the following example is OK: + * + * ``` + * void foo() { + * synchronized(x) { + * bar(); + * } + * } + * + * void bar() { + * x.notify() + * } + * ``` + */ +private +predicate synchronizedCallable(Callable c) { + c.isSynchronized() or + ( + c.isPrivate() and + forall(MethodAccess parent | parent.getCallee() = c | + synchronizedThisAccess(parent, c.getDeclaringType()) + ) + ) +} + +/** + * Auxiliary predicate for `unsynchronizedExplicitThisAccess` and + * `unsynchronizedImplicitThisAccess`. It holds if there is an + * enclosing synchronization context of the appropriate type. For + * example, if the method call is `MyClass.wait()`, then the predicate + * holds if there is an enclosing synchronization on `MyClass.this`. + */ +private +predicate synchronizedThisAccess(MethodAccess ma, Type thisType) { + // Are we inside a synchronized method? + exists(Callable c | + c = ma.getEnclosingCallable() and + c.getDeclaringType() = thisType and + synchronizedCallable(c) + ) or + // Is there an enclosing `synchronized` statement? + exists(SynchronizedStmt s, ThisAccess x | + s.getAChild*() = ma.getEnclosingStmt() and + s.getExpr() = x and + x.getType() = thisType + ) +} + +/** + * Auxiliary predicate for `unsynchronizedVarAccess`. Holds if + * there is an enclosing `synchronized` statement on the variable. + */ +predicate synchronizedVarAccess(VarAccess x) { + exists(SynchronizedStmt s, VarAccess y | + s.getAChild*() = x.getEnclosingStmt() and + s.getExpr() = y and + y.getVariable() = x.getVariable() and + y.toString() = x.toString() + ) +} + +/** + * This predicate holds if the `MethodAccess` is a qualified call, + * such as `this.wait()`, and it is not inside a synchronized statement + * or method. + */ +private +predicate unsynchronizedExplicitThisAccess(MethodAccess ma) { + exists(ThisAccess x | + x = ma.getQualifier() and + not synchronizedThisAccess(ma, x.getType()) + ) +} + +/** + * Holds if the `MethodAccess` is an unqualified call, + * such as `wait()`, and it is not inside a synchronized statement + * or method. + */ +private +predicate unsynchronizedImplicitThisAccess(MethodAccess ma) { + not ma.hasQualifier() and + not synchronizedThisAccess(ma, ma.getEnclosingCallable().getDeclaringType()) +} + +/** + * Holds if the `MethodAccess` is on a variable, + * such as `x.wait()`, and it is not inside a synchronized statement. + */ +private +predicate unsynchronizedVarAccess(MethodAccess ma) { + exists(VarAccess x | + x = ma.getQualifier() and + not synchronizedVarAccess(x) + ) +} + +from MethodAccess ma, Method m +where + m = ma.getMethod() and + m instanceof MethodRequiresSynch and + ( + unsynchronizedExplicitThisAccess(ma) or + unsynchronizedImplicitThisAccess(ma) or + unsynchronizedVarAccess(ma) + ) +select ma, "Calling " + m.getName() + " on an unsynchronized object." diff --git a/java/ql/src/Likely Bugs/Concurrency/PriorityCalls.qhelp b/java/ql/src/Likely Bugs/Concurrency/PriorityCalls.qhelp new file mode 100644 index 00000000000..6cba8c9968f --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/PriorityCalls.qhelp @@ -0,0 +1,46 @@ + + + + + +

    Specifying thread priorities using calls to Thread.setPriority +and Thread.getPriority is not portable +and may have adverse consequences such as starvation. +

    + +
    + + +

    Avoid setting thread priorities to control interactions between threads. +Using the default thread priority should be sufficient for most applications. +

    + +

    However, if you need to enforce a specific synchronization order, use one of the following +alternatives:

    + +
      +
    • Waiting for a notification using the wait and notifyAll methods
    • +
    • Using the java.util.concurrent library
    • +
    + +

    In some cases, calls to Thread.sleep may be appropriate to temporarily stop +execution (provided that there is no possibility for race conditions), but this is not generally +recommended.

    + +
    + + + +
  • + J. Bloch, Effective Java (second edition), Item 72. Addison-Wesley, 2008. +
  • +
  • + Inform IT: + Adding Multithreading Capability to Your Java Applications. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Concurrency/PriorityCalls.ql b/java/ql/src/Likely Bugs/Concurrency/PriorityCalls.ql new file mode 100644 index 00000000000..4a0612c74c6 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/PriorityCalls.ql @@ -0,0 +1,30 @@ +/** + * @name Explicit thread priority + * @description Setting thread priorities to control interactions between threads is not portable, + * and may not have the desired effect. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/thread-priority + * @tags portability + * correctness + * concurrency + */ +import java + +class PriorityMethod extends Method { + PriorityMethod() { + (this.getName() = "setPriority" or this.getName() = "getPriority") and + this.getDeclaringType().hasQualifiedName("java.lang","Thread") + } +} + +class PriorityMethodAccess extends MethodAccess { + PriorityMethodAccess() { + this.getMethod() instanceof PriorityMethod + } +} + +from PriorityMethodAccess ma +where ma.getCompilationUnit().fromSource() +select ma, "Avoid using thread priorities. The effect is unpredictable and not portable." diff --git a/java/ql/src/Likely Bugs/Concurrency/SleepWithLock.java b/java/ql/src/Likely Bugs/Concurrency/SleepWithLock.java new file mode 100644 index 00000000000..dd7326064b0 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/SleepWithLock.java @@ -0,0 +1,31 @@ +class StorageThread implements Runnable{ + public static Integer counter = 0; + private static final Object LOCK = new Object(); + + public void run() { + System.out.println("StorageThread started."); + synchronized(LOCK) { // "LOCK" is locked just before the thread goes to sleep + try { + Thread.sleep(5000); + } catch (InterruptedException e) { ... } + } + System.out.println("StorageThread exited."); + } +} + +class OtherThread implements Runnable{ + public void run() { + System.out.println("OtherThread started."); + synchronized(StorageThread.LOCK) { + StorageThread.counter++; + } + System.out.println("OtherThread exited."); + } +} + +public class SleepWithLock { + public static void main(String[] args) { + new Thread(new StorageThread()).start(); + new Thread(new OtherThread()).start(); + } +} diff --git a/java/ql/src/Likely Bugs/Concurrency/SleepWithLock.qhelp b/java/ql/src/Likely Bugs/Concurrency/SleepWithLock.qhelp new file mode 100644 index 00000000000..0868f214869 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/SleepWithLock.qhelp @@ -0,0 +1,47 @@ + + + +

    Calling Thread.sleep with a lock held may lead to very poor performance +or even deadlock. This is because Thread.sleep does not cause a thread to release its locks.

    + +
    + + +

    +Thread.sleep should be called only outside of a synchronized block. +However, a better way for threads to yield execution time to other threads may be to use either of +the following solutions:

    + +
      +
    • The java.util.concurrent library
    • +
    • The wait and notifyAll methods
    • +
    + +
    + + +

    In the following example of the problem, two threads, StorageThread and OtherThread, +are started. Both threads output a message to show that they have started but then +StorageThread locks counter and goes to sleep. The lock prevents +OtherThread from locking counter, so it has to wait until +StorageThread has woken up and unlocked counter before it can continue.

    + + + +

    To avoid this problem, StorageThread should call Thread.sleep outside +the synchronized block instead, so that counter is unlocked.

    + +
    + + + +
  • Java API Documentation: Thread.sleep(), + Object.wait(), + Object.notifyAll(), + java.util.concurrent.
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Concurrency/SleepWithLock.ql b/java/ql/src/Likely Bugs/Concurrency/SleepWithLock.ql new file mode 100644 index 00000000000..e0aa8dfc579 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/SleepWithLock.ql @@ -0,0 +1,25 @@ +/** + * @name Sleep with lock held + * @description Calling 'Thread.sleep' with a lock held may lead to very poor + * performance or even deadlock. + * @kind problem + * @problem.severity error + * @precision medium + * @id java/sleep-with-lock-held + * @tags reliability + * correctness + * concurrency + * external/cwe/cwe-833 + */ +import java + +from MethodAccess ma, Method sleep +where + ma.getMethod() = sleep and + sleep.hasName("sleep") and + sleep.getDeclaringType().hasQualifiedName("java.lang", "Thread") and + ( + ma.getEnclosingStmt().getParent*() instanceof SynchronizedStmt or + ma.getEnclosingCallable().isSynchronized() + ) +select ma, "sleep() with lock held." diff --git a/java/ql/src/Likely Bugs/Concurrency/StartInConstructor.java b/java/ql/src/Likely Bugs/Concurrency/StartInConstructor.java new file mode 100644 index 00000000000..23276ad7573 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/StartInConstructor.java @@ -0,0 +1,30 @@ +class Super { + public Super() { + new Thread() { + public void run() { + System.out.println(Super.this.toString()); + } + }.start(); // BAD: The thread is started in the constructor of 'Super'. + } + + public String toString() { + return "hello"; + } +} + +class Test extends Super { + private String name; + public Test(String nm) { + // The thread is started before + // this line is run + this.name = nm; + } + + public String toString() { + return super.toString() + " " + name; + } + + public static void main(String[] args) { + new Test("my friend"); + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Concurrency/StartInConstructor.qhelp b/java/ql/src/Likely Bugs/Concurrency/StartInConstructor.qhelp new file mode 100644 index 00000000000..1d0d5b0a1fb --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/StartInConstructor.qhelp @@ -0,0 +1,49 @@ + + + + + +

    Starting a thread within a constructor may cause unexpected results. If the class is extended, +the thread may start before the subclass constructor has completed its initialization, +which may not be intended.

    + +
    + + +

    Avoid starting threads in constructors. +Typically, the constructor of a class only constructs the thread object, +and a separate start method should be provided to start the thread object +created by the constructor. +

    + +
    + + +

    In the following example, because the Test constructor implicitly calls the +Super constructor, the thread created in the Super constructor may start +before this.name has been initialized. Therefore, the program may +output "hello " followed by a null string. +

    + + + +

    In the following modified example, the thread created in the Super constructor is not started +within the constructor; main starts the thread after this.name has been +initialized. This results in the program outputting "hello my friend".

    + + + +
    + + + +
  • + IBM developerWorks: + Don't start threads from within constructors. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Concurrency/StartInConstructor.ql b/java/ql/src/Likely Bugs/Concurrency/StartInConstructor.ql new file mode 100644 index 00000000000..7a1f6ef12c6 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/StartInConstructor.ql @@ -0,0 +1,34 @@ +/** + * @name Start of thread in constructor + * @description Starting a thread within a constructor may cause the thread to start before + * any subclass constructor has completed its initialization, causing unexpected + * results. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/thread-start-in-constructor + * @tags reliability + * correctness + * concurrency + */ +import java + +/** + * Holds if this type is either `final` or + * `private` and without subtypes. + */ +private predicate cannotBeExtended(RefType t) { + t.isFinal() + or + // If the class is private, all possible subclasses are known. + t.isPrivate() and not exists(RefType sub | sub != t | sub.getAnAncestor() = t) +} + +from MethodAccess m, Constructor c, Class clazz +where + m.getMethod().getDeclaringType().hasQualifiedName("java.lang", "Thread") and + m.getMethod().getName() = "start" and + m.getEnclosingCallable() = c and + c.getDeclaringType() = clazz and + not cannotBeExtended(clazz) +select m, "Class $@ starts a thread in its constructor.", clazz, clazz.getName() diff --git a/java/ql/src/Likely Bugs/Concurrency/StartInConstructorGood.java b/java/ql/src/Likely Bugs/Concurrency/StartInConstructorGood.java new file mode 100644 index 00000000000..5f0a37a4ae8 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/StartInConstructorGood.java @@ -0,0 +1,34 @@ +class Super { + Thread thread; + public Super() { + thread = new Thread() { + public void run() { + System.out.println(Super.this.toString()); + } + }; + } + + public void start() { // good + thread.start(); + } + + public String toString() { + return "hello"; + } +} + +class Test extends Super { + private String name; + public Test(String nm) { + this.name = nm; + } + + public String toString() { + return super.toString() + " " + name; + } + + public static void main(String[] args) { + Test t = new Test("my friend"); + t.start(); + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Concurrency/SynchOnBoxedType.java b/java/ql/src/Likely Bugs/Concurrency/SynchOnBoxedType.java new file mode 100644 index 00000000000..70fac6b5baa --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/SynchOnBoxedType.java @@ -0,0 +1,28 @@ +class BadSynchronize{ + + class ThreadA extends Thread{ + private String value = "lock" + + public void run(){ + synchronized(value){ + //... + } + } + } + + class ThreadB extends Thread{ + private String value = "lock" + + public void run(){ + synchronized(value){ + //... + } + } + } + + public void run(){ + new ThreadA().start(); + new ThreadB().start(); + } + +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Concurrency/SynchOnBoxedType.qhelp b/java/ql/src/Likely Bugs/Concurrency/SynchOnBoxedType.qhelp new file mode 100644 index 00000000000..542c1f83e52 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/SynchOnBoxedType.qhelp @@ -0,0 +1,60 @@ + + + + + +

    +Code should not synchronize on a variable or field of a boxed type (for example +Integer, Boolean) or of type String since +it is likely to contain an object that is used throughout the program. For example, +Boolean.TRUE holds a single instance that will be used in many places +throughout the program: whenever true is autoboxed or a call to +Boolean.valueOf is made with true as an argument the +same instance of Boolean is returned. It is therefore likely that +two classes synchronizing on a field of type Boolean will end up +synchronizing on the same object. This may lead to deadlock or threads being +blocked unnecessarily. +

    + +
    + + +

    +Synchronize on a specific lock object instead of using an object with a boxed type. +

    + +
    + + +

    +In the following example, the intention is to allow ThreadA and +ThreadB to run at the same time. Unfortunately, +ThreadA.lock and ThreadB.lock both refer to the same +object (that is, the interned value of the String +"lock") so the synchronized blocks in their run methods can not be +executed concurrently. +

    + + + +

    +In the following example, the approach recommended above is shown. A separate +lock object is created for each thread allowing them to execute concurrently. +

    + + + +
    + + + +
  • + The CERT Oracle Secure Coding Standard for Java: + LCK01-J. Do not synchronize on objects that may be reused, +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Concurrency/SynchOnBoxedType.ql b/java/ql/src/Likely Bugs/Concurrency/SynchOnBoxedType.ql new file mode 100644 index 00000000000..a217d019f32 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/SynchOnBoxedType.ql @@ -0,0 +1,23 @@ +/** + * @name Synchronization on boxed types or strings + * @description Synchronizing on boxed types or strings may lead to + * deadlock since an instance of that type is likely to + * be shared between many parts of the program. + * @kind problem + * @problem.severity error + * @precision very-high + * @id java/sync-on-boxed-types + * @tags reliability + * correctness + * concurrency + * language-features + * external/cwe/cwe-662 + */ + +import java + +from SynchronizedStmt synch, Type type +where + synch.getExpr().getType() = type and + (type instanceof BoxedType or type instanceof TypeString) +select synch.getExpr(), "Do not synchronize on objects of type " + type + "." diff --git a/java/ql/src/Likely Bugs/Concurrency/SynchOnBoxedTypeGood.java b/java/ql/src/Likely Bugs/Concurrency/SynchOnBoxedTypeGood.java new file mode 100644 index 00000000000..64f7d7e1cb2 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/SynchOnBoxedTypeGood.java @@ -0,0 +1,28 @@ +class GoodSynchronize{ + + class ThreadA extends Thread{ + private Object lock = new Object(); + + public void run(){ + synchronized(lock){ + //... + } + } + } + + class ThreadB extends Thread{ + private Object lock = new Object(); + + public void run(){ + synchronized(lock){ + //... + } + } + } + + public void run(){ + new ThreadA().start(); + new ThreadB().start(); + } + +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.qhelp b/java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.qhelp new file mode 100644 index 00000000000..d63b49dfb44 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.qhelp @@ -0,0 +1,32 @@ + + + + + +

    If a class has a synchronized set method and a similarly-named get +method is not also synchronized, calls to the get method may not return a consistent +state for the object. +

    + +
    + + +

    +Synchronize read operations as well as write operations. You should usually synchronize the +get method. +

    + +
    + + + +
  • + The Java Language Specification: + Synchronization. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.ql b/java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.ql new file mode 100644 index 00000000000..4fbd06097b4 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.ql @@ -0,0 +1,57 @@ +/** + * @name Inconsistent synchronization of getter and setter + * @description If a class has a synchronized 'set' method, and a similarly-named 'get' method is + * not also synchronized, calls to the 'get' method may not return a consistent state + * for the object. + * @kind problem + * @problem.severity error + * @precision very-high + * @id java/unsynchronized-getter + * @tags reliability + * correctness + * concurrency + * language-features + * external/cwe/cwe-413 + * external/cwe/cwe-662 + */ +import java + +/** + * Holds if this method is synchronized by a `synchronized(Foo.class){...}` block + * (for static methods) or a `synchronized(this){...}` block (for instance methods). + */ +predicate isSynchronizedByBlock(Method m) { + exists(SynchronizedStmt sync, Expr on | + sync = m.getBody().getAChild*() and on = sync.getExpr().getProperExpr() + | + if m.isStatic() then + on.(TypeLiteral).getTypeName().getType() = m.getDeclaringType() + else + on.(ThisAccess).getType().(RefType).getSourceDeclaration() = m.getDeclaringType() + ) +} + +/** + * Holds if `get` is a getter method for a volatile field that `set` writes to. + * + * In this case, even if `set` is synchronized and `get` is not, `get` will never see stale + * values for the field, so synchronization is optional. + */ +predicate bothAccessVolatileField(Method set, Method get) { + exists(Field f | f.isVolatile() | + f = get.(GetterMethod).getField() and + f.getAnAccess().(FieldWrite).getEnclosingCallable() = set + ) +} + +from Method set, Method get +where + set.getDeclaringType() = get.getDeclaringType() and + set.getName().matches("set%") and + get.getName() = "get"+set.getName().substring(3,set.getName().length()) and + set.isSynchronized() and + not (get.isSynchronized() or isSynchronizedByBlock(get)) and + not bothAccessVolatileField(set, get) and + set.fromSource() +select get, "This get method is unsynchronized, but the corresponding $@ is synchronized.", + set, "set method" diff --git a/java/ql/src/Likely Bugs/Concurrency/SynchWriteObject.qhelp b/java/ql/src/Likely Bugs/Concurrency/SynchWriteObject.qhelp new file mode 100644 index 00000000000..719a1c5e69e --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/SynchWriteObject.qhelp @@ -0,0 +1,34 @@ + + + + + +

    Classes with a synchronized writeObject method +but no other synchronized methods usually lack a sufficient level of synchronization. +If any mutable state of this class can be modified without proper synchronization, +the serialization using the writeObject method may result in an inconsistent state. +

    + +
    + + +

    See if synchronization is necessary +on methods other than writeOject to make the class thread-safe. +Any methods that access or modify the state of an object of this class +should usually be synchronized as well. +

    + +
    + + + +
  • + The Java Language Specification: + Synchronization. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Concurrency/SynchWriteObject.ql b/java/ql/src/Likely Bugs/Concurrency/SynchWriteObject.ql new file mode 100644 index 00000000000..c3189f65d6e --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/SynchWriteObject.ql @@ -0,0 +1,29 @@ +/** + * @name Inconsistent synchronization for writeObject() + * @description Classes with a synchronized 'writeObject' method but no other + * synchronized methods usually lack a sufficient level of synchronization. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/inconsistent-sync-writeobject + * @tags reliability + * correctness + * concurrency + * language-features + * external/cwe/cwe-662 + */ +import java + +from Method m +where + m.getDeclaringType().getASupertype*() instanceof TypeSerializable and + m.hasName("writeObject") and + m.getNumberOfParameters() = 1 and + m.getAParamType().(Class).hasQualifiedName("java.io","ObjectOutputStream") and + m.isSynchronized() and + not exists(Method s | + m.getDeclaringType().inherits(s) and + s.isSynchronized() and + s != m + ) +select m, "Class's writeObject() method is synchronized but nothing else is." diff --git a/java/ql/src/Likely Bugs/Concurrency/UnreleasedLock.java b/java/ql/src/Likely Bugs/Concurrency/UnreleasedLock.java new file mode 100644 index 00000000000..aeea1ce9892 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/UnreleasedLock.java @@ -0,0 +1,10 @@ +public void m() { + lock.lock(); + // A + try { + // ... method body + } finally { + // B + lock.unlock(); + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Concurrency/UnreleasedLock.qhelp b/java/ql/src/Likely Bugs/Concurrency/UnreleasedLock.qhelp new file mode 100644 index 00000000000..b2e1ced8536 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/UnreleasedLock.qhelp @@ -0,0 +1,50 @@ + + + + +

    +When a thread acquires a lock it must make sure to unlock it again; +failing to do so can lead to deadlocks. If a lock allows a thread to acquire +it multiple times, for example java.util.concurrent.locks.ReentrantLock, +then the number of locks must match the number of unlocks in order to fully +release the lock. +

    +
    + + +

    +It is recommended practice always to immediately follow a call to lock +with a try block and place the call to unlock inside the +finally block. Beware of calls inside the finally block +that could cause exceptions, as this may result in skipping the call to unlock. +

    +
    + + + +

    +The typical pattern for using locks safely looks like this: +

    + + + +

    +If any code that can cause a premature method exit (for example by throwing an +exception) is inserted at either point A or B then +the method might not unlock, so this should be avoided. +

    + +
    + + +
  • +Java API Documentation: +java.util.concurrent.locks.Lock, +java.util.concurrent.locks.ReentrantLock. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Concurrency/UnreleasedLock.ql b/java/ql/src/Likely Bugs/Concurrency/UnreleasedLock.ql new file mode 100644 index 00000000000..f16844588bd --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/UnreleasedLock.ql @@ -0,0 +1,152 @@ +/** + * @name Unreleased lock + * @description A lock that is acquired one or more times without a matching number of unlocks + * may cause a deadlock. + * @kind problem + * @problem.severity error + * @precision medium + * @id java/unreleased-lock + * @tags reliability + * security + * external/cwe/cwe-764 + * external/cwe/cwe-833 + */ +import java +import semmle.code.java.controlflow.Guards +import semmle.code.java.dataflow.SSA +import semmle.code.java.frameworks.Mockito + +class LockType extends RefType { + LockType() { + this.getAMethod().hasName("lock") and + this.getAMethod().hasName("unlock") + } + + Method getLockMethod() { + result.getDeclaringType() = this and + (result.hasName("lock") or result.hasName("tryLock")) + } + + Method getUnlockMethod() { + result.getDeclaringType() = this and + result.hasName("unlock") + } + + Method getIsHeldByCurrentThreadMethod() { + result.getDeclaringType() = this and + result.hasName("isHeldByCurrentThread") + } + + MethodAccess getLockAccess() { + result.getMethod() = this.getLockMethod() and + // Not part of a Mockito verification call + not result instanceof MockitoVerifiedMethodAccess + } + + MethodAccess getUnlockAccess() { + result.getMethod() = this.getUnlockMethod() and + // Not part of a Mockito verification call + not result instanceof MockitoVerifiedMethodAccess + } + + MethodAccess getIsHeldByCurrentThreadAccess() { + result.getMethod() = this.getIsHeldByCurrentThreadMethod() and + // Not part of a Mockito verification call + not result instanceof MockitoVerifiedMethodAccess + } +} + +predicate lockBlock(LockType t, BasicBlock b, int locks) { + locks = strictcount(int i | b.getNode(i) = t.getLockAccess()) +} + +predicate unlockBlock(LockType t, BasicBlock b, int unlocks) { + unlocks = strictcount(int i | b.getNode(i) = t.getUnlockAccess()) +} + +/** + * A block `b` that locks and/or unlocks `t` a number of times; `netlocks` + * equals the number of locks minus the number of unlocks. + */ +predicate lockUnlockBlock(LockType t, BasicBlock b, int netlocks) { + lockBlock(t, b, netlocks) and not unlockBlock(t, b, _) or + exists(int unlocks | + not lockBlock(t, b, _) and unlockBlock(t, b, unlocks) and netlocks = -unlocks + ) or + exists(int locks, int unlocks | + lockBlock(t, b, locks) and unlockBlock(t, b, unlocks) and netlocks = locks - unlocks + ) +} + +/** + * A call to `lock` or `tryLock` on `t` that fails with an exception or the value `false` + * resulting in a CFG edge from `lockblock` to `exblock`. + */ +predicate failedLock(LockType t, BasicBlock lockblock, BasicBlock exblock) { + exists(ControlFlowNode lock | + lock = lockblock.getLastNode() and + ( + lock = t.getLockAccess() + or + exists(SsaExplicitUpdate lockbool | + // Using the value of `t.getLockAccess()` ensures that it is a `tryLock` call. + lock = lockbool.getAUse() and + lockbool.getDefiningExpr().(VariableAssign).getSource() = t.getLockAccess() + ) + ) + and + ( + lock.getAnExceptionSuccessor() = exblock or + lock.(ConditionNode).getAFalseSuccessor() = exblock + ) + ) +} + +/** + * A call to `isHeldByCurrentThread` on `t` in `checkblock` that has `falsesucc` as the false + * successor. + */ +predicate heldByCurrentThreadCheck(LockType t, BasicBlock checkblock, BasicBlock falsesucc) { + exists(ConditionBlock conditionBlock | + conditionBlock.getCondition() = t.getIsHeldByCurrentThreadAccess() + | + conditionBlock.getBasicBlock() = checkblock and + conditionBlock.getTestSuccessor(false) = falsesucc + ) +} + +/** + * A control flow path from a locking call in `src` to `b` such that the number of + * locks minus the number of unlocks along the way is positive and equal to `locks`. + */ +predicate blockIsLocked(LockType t, BasicBlock src, BasicBlock b, int locks) { + lockUnlockBlock(t, b, locks) and src = b and locks > 0 + or + exists(BasicBlock pred, int predlocks, int curlocks, int failedlock | pred = b.getABBPredecessor() | + // The number of net locks from the `src` block to the predecessor block `pred` is `predlocks`. + blockIsLocked(t, src, pred, predlocks) and + /* + * The recursive call ensures that at least one lock is held, so do not consider the false + * successor of the `isHeldByCurrentThread()` check. + */ + not heldByCurrentThreadCheck(t, pred, b) and + // Count a failed lock as an unlock so the net is zero. + ( if failedLock(t, pred, b) then failedlock = 1 else failedlock = 0 ) and + ( not lockUnlockBlock(t, b, _) and curlocks = 0 or + lockUnlockBlock(t, b, curlocks) + ) and + locks = predlocks + curlocks - failedlock and locks > 0 and + // Arbitrary bound in order to fail gracefully in case of locking in a loop. + locks < 10 + ) +} + +from Callable c, LockType t, BasicBlock src, BasicBlock exit, MethodAccess lock +where + // Restrict results to those methods that actually attempt to unlock. + t.getUnlockAccess().getEnclosingCallable() = c and + blockIsLocked(t, src, exit, _) and + exit.getLastNode() = c and + lock = src.getANode() and lock = t.getLockAccess() +select + lock, "This lock might not be unlocked or might be locked more times than it is unlocked." diff --git a/java/ql/src/Likely Bugs/Concurrency/WaitOutsideLoop.java b/java/ql/src/Likely Bugs/Concurrency/WaitOutsideLoop.java new file mode 100644 index 00000000000..aa7e9d9a08d --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/WaitOutsideLoop.java @@ -0,0 +1,4 @@ +synchronized (obj) { + while () obj.wait(); + // condition is true, perform appropriate action ... +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Concurrency/WaitOutsideLoop.qhelp b/java/ql/src/Likely Bugs/Concurrency/WaitOutsideLoop.qhelp new file mode 100644 index 00000000000..682cc09f38d --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/WaitOutsideLoop.qhelp @@ -0,0 +1,39 @@ + + + +

    Calling Object.wait outside of a loop may cause problems because the thread does +not go back to sleep after a spurious wake-up call. This results in the program continuing before +the expected condition is met. +

    + +
    + + +

    Ensure that wait is called within a loop that tests for the condition that the +thread is waiting for. This ensures that the program only proceeds to execute when the +relevant condition is true. Note that the thread that calls wait on an object must be +the owner of that object's monitor. +

    + +
    + + +

    In the following example, obj.wait is called within a while loop +until the condition is true, at which point the program continues with the next statement after +the loop:

    + + + +
    + + + +
  • J. Bloch, Effective Java (second edition), p. 276. Addison-Wesley, 2008.
  • +
  • Java API Documentation: Object.wait().
  • +
  • The Java Tutorials: Guarded Blocks.
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Concurrency/WaitOutsideLoop.ql b/java/ql/src/Likely Bugs/Concurrency/WaitOutsideLoop.ql new file mode 100644 index 00000000000..36d1f4b2dd3 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/WaitOutsideLoop.ql @@ -0,0 +1,26 @@ +/** + * @name Wait outside loop + * @description Calling 'wait' outside a loop may result in the program continuing before the + * expected condition is met. + * @kind problem + * @problem.severity error + * @precision low + * @id java/wait-outside-loop + * @tags reliability + * correctness + * concurrency + */ +import java + +class WaitMethod extends Method { + WaitMethod() { + this.getName() = "wait" and + this.getDeclaringType().getQualifiedName() = "java.lang.Object" + } +} + +from MethodAccess ma +where + ma.getMethod() instanceof WaitMethod and + not exists(LoopStmt s | ma.getEnclosingStmt().getParent*() = s) +select ma, "To avoid spurious wake-ups, 'wait' should only be called inside a loop." diff --git a/java/ql/src/Likely Bugs/Concurrency/WaitWithTwoLocks.java b/java/ql/src/Likely Bugs/Concurrency/WaitWithTwoLocks.java new file mode 100644 index 00000000000..712fe53b05e --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/WaitWithTwoLocks.java @@ -0,0 +1,36 @@ +class WaitWithTwoLocks { + + private final Object idLock = new Object(); + private int id = 0; + + private final Object textLock = new Object(); + private String text = null; + + public void printText() { + synchronized (idLock) { + synchronized (textLock) { + while(text == null) + try { + textLock.wait(); // The lock on "textLock" is released but not the + // lock on "idLock". + } + catch (InterruptedException e) { ... } + System.out.println(id + ":" + text); + text = null; + textLock.notifyAll(); + } + } + } + + public void setText(String mesg) { + synchronized (idLock) { // "setText" needs a lock on "idLock" but "printText" already + // holds a lock on "idLock", leading to deadlock + synchronized (textLock) { + id++; + text = mesg; + idLock.notifyAll(); + textLock.notifyAll(); + } + } + } + } diff --git a/java/ql/src/Likely Bugs/Concurrency/WaitWithTwoLocks.qhelp b/java/ql/src/Likely Bugs/Concurrency/WaitWithTwoLocks.qhelp new file mode 100644 index 00000000000..ee84d36578a --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/WaitWithTwoLocks.qhelp @@ -0,0 +1,45 @@ + + + +

    Calling Object.wait while two locks are held +may cause deadlock, because only one lock is released by wait. +

    + +
    + + +

    See if one of the locks should continue to be held while waiting for a condition on the other +lock. If not, release one of the locks before calling Object.wait.

    + +
    + + +

    In the following example of the problem, printText locks both idLock and +textLock before it reads the value of text. It +then calls textLock.wait, which releases the lock on textLock. +However, setText needs to lock idLock but it cannot because +idLock is still locked by printText. Thus, deadlock is caused.

    + + + +

    In the following modification of the above example, id and text are +included in the class Message. The method printText +synchronizes on the field message before it reads the value of message.text. It then calls +message.wait, which releases the lock on message. +This enables setText to lock message so that it +can proceed.

    + + + +
    + + + +
  • Java API Documentation: Object.wait().
  • + + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Concurrency/WaitWithTwoLocks.ql b/java/ql/src/Likely Bugs/Concurrency/WaitWithTwoLocks.ql new file mode 100644 index 00000000000..25ebe28b72f --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/WaitWithTwoLocks.ql @@ -0,0 +1,29 @@ +/** + * @name Wait with two locks held + * @description Calling 'Object.wait' while two locks are held may cause deadlock. + * @kind problem + * @problem.severity error + * @precision low + * @id java/wait-with-two-locks + * @tags reliability + * correctness + * concurrency + * external/cwe/cwe-833 + */ +import java + +/** A `synchronized` method or statement. */ +class Synched extends StmtParent { + Synched() { + this.(Method).isSynchronized() or + this instanceof SynchronizedStmt + } +} + +from MethodAccess ma, SynchronizedStmt synch +where + ma.getMethod().hasName("wait") and + ma.getMethod().getDeclaringType().hasQualifiedName("java.lang", "Object") and + ma.getEnclosingStmt().getParent*() = synch and + synch.getParent+() instanceof Synched +select ma, "wait() with two locks held." diff --git a/java/ql/src/Likely Bugs/Concurrency/WaitWithTwoLocksGood.java b/java/ql/src/Likely Bugs/Concurrency/WaitWithTwoLocksGood.java new file mode 100644 index 00000000000..a736310f931 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/WaitWithTwoLocksGood.java @@ -0,0 +1,30 @@ +class WaitWithTwoLocksGood { + + private static class Message { + public int id = 0; + public String text = null; + } + + private final Message message = new Message(); + + public void printText() { + synchronized (message) { + while(message.txt == null) + try { + message.wait(); + } + catch (InterruptedException e) { ... } + System.out.println(message.id + ":" + message.text); + message.text = null; + message.notifyAll(); + } + } + + public void setText(String mesg) { + synchronized (message) { + message.id++; + message.text = mesg; + message.notifyAll(); + } + } + } diff --git a/java/ql/src/Likely Bugs/Concurrency/YieldCalls.qhelp b/java/ql/src/Likely Bugs/Concurrency/YieldCalls.qhelp new file mode 100644 index 00000000000..a26e715e10a --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/YieldCalls.qhelp @@ -0,0 +1,42 @@ + + + + + +

    The method Thread.yield is a non-portable and underspecified operation. +It may have no effect, and is not a reliable way to prevent a thread from taking up too much execution time. +

    + +
    + + +

    Use alternative ways of preventing a thread from taking up too much execution time. +Communication between threads should normally be implemented using some form of waiting for a notification using +the wait and notifyAll methods or by using the java.util.concurrent library. +

    +

    +In some cases, calls to Thread.sleep may be appropriate to temporarily cease execution +(provided there is no possibility for race conditions), but this is not generally recommended. +

    + +
    + + + +
  • + J. Bloch, Effective Java (second edition), Item 72. Addison-Wesley, 2008. +
  • +
  • + Java API Documentation: + Thread.yield(), + Object.wait(), + Object.notifyAll(), + java.util.concurrent, + Thread.sleep(). +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Concurrency/YieldCalls.ql b/java/ql/src/Likely Bugs/Concurrency/YieldCalls.ql new file mode 100644 index 00000000000..7ccc3b83047 --- /dev/null +++ b/java/ql/src/Likely Bugs/Concurrency/YieldCalls.ql @@ -0,0 +1,31 @@ +/** + * @name Call to Thread.yield() + * @description Calling 'Thread.yield' may have no effect, and is not a reliable way to prevent a + * thread from taking up too much execution time. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/thread-yield-call + * @tags reliability + * correctness + * concurrency + */ +import java + +class YieldMethod extends Method { + YieldMethod() { + this.getName() = "yield" and + this.getDeclaringType().hasQualifiedName("java.lang","Thread") + } +} + +class YieldMethodAccess extends MethodAccess { + YieldMethodAccess() { + this.getMethod() instanceof YieldMethod + } +} + +from YieldMethodAccess yield +where yield.getCompilationUnit().fromSource() +select yield, + "Do not use Thread.yield(). It is non-portable and will most likely not have the desired effect." diff --git a/java/ql/src/Likely Bugs/Finalization/NullifiedSuperFinalize.java b/java/ql/src/Likely Bugs/Finalization/NullifiedSuperFinalize.java new file mode 100644 index 00000000000..ddd6d5c7f4f --- /dev/null +++ b/java/ql/src/Likely Bugs/Finalization/NullifiedSuperFinalize.java @@ -0,0 +1,30 @@ +class LocalCache { + private Collection localResources; + + //... + + protected void finalize() throws Throwable { + for (NativeResource r : localResources) { + r.dispose(); + } + }; +} + +class WrongCache extends LocalCache { + //... + @Override + protected void finalize() throws Throwable { + // BAD: Empty 'finalize', which does not call 'super.finalize'. + // Native resources in LocalCache are not disposed of. + } +} + +class RightCache extends LocalCache { + //... + @Override + protected void finalize() throws Throwable { + // GOOD: 'finalize' calls 'super.finalize'. + // Native resources in LocalCache are disposed of. + super.finalize(); + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Finalization/NullifiedSuperFinalize.qhelp b/java/ql/src/Likely Bugs/Finalization/NullifiedSuperFinalize.qhelp new file mode 100644 index 00000000000..35b39977b46 --- /dev/null +++ b/java/ql/src/Likely Bugs/Finalization/NullifiedSuperFinalize.qhelp @@ -0,0 +1,58 @@ + + + + + +

    +A finalize method that overrides the finalizer of a superclass but does not call +super.finalize may leave system resources undisposed of or cause other cleanup actions +to be left undone. +

    + +
    + + +

    +Make sure that all finalize methods call super.finalize to ensure that the +finalizer of its superclass is executed. Finalizer chaining is not automatic in Java. +

    + +

    +It is also possible to defend against subclasses that do not call super.finalize by putting the cleanup code into a +finalizer guardian instead of the finalize method. A finalizer guardian is an anonymous object instance +that contains the cleanup code for the enclosing object in its finalize method. +The only reference to the finalizer guardian is stored in a private field of the enclosing instance, +which means that both the guardian and the enclosing instance can be finalized at the same time. +This way, a subclass cannot block the execution of the cleanup code by not calling super.finalize. +

    + +
    + + +

    In the following example, WrongCache.finalize does not call super.finalize, +which means that native resources are not disposed of. However, RightCache.finalize +does call super.finalize, which means that native resources are disposed of.

    + + + +

    The following example shows a finalizer guardian.

    + + + +
    + + + +
  • + Java 7 API Documentation: + Object.finalize(). +
  • +
  • + J. Bloch, Effective Java (second edition), Item 7. Addison-Wesley, 2008. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Finalization/NullifiedSuperFinalize.ql b/java/ql/src/Likely Bugs/Finalization/NullifiedSuperFinalize.ql new file mode 100644 index 00000000000..ad1a5c39ac5 --- /dev/null +++ b/java/ql/src/Likely Bugs/Finalization/NullifiedSuperFinalize.ql @@ -0,0 +1,22 @@ +/** + * @name Finalizer inconsistency + * @description A 'finalize' method that does not call 'super.finalize' may leave + * cleanup actions undone. + * @kind problem + * @problem.severity error + * @precision medium + * @id java/missing-super-finalize + * @tags reliability + * maintainability + * external/cwe/cwe-568 + */ +import java + +from FinalizeMethod m, Class c, FinalizeMethod mSuper, Class cSuper +where + m.getDeclaringType() = c and + mSuper.getDeclaringType() = cSuper and + c.getASupertype+() = cSuper and + not cSuper instanceof TypeObject and + not exists(m.getBody().getAChild()) +select m, "Finalize in " + c.getName() + " nullifies finalize in " + cSuper.getName() + "." diff --git a/java/ql/src/Likely Bugs/Finalization/NullifiedSuperFinalizeGuarded.java b/java/ql/src/Likely Bugs/Finalization/NullifiedSuperFinalizeGuarded.java new file mode 100644 index 00000000000..847c28f2a46 --- /dev/null +++ b/java/ql/src/Likely Bugs/Finalization/NullifiedSuperFinalizeGuarded.java @@ -0,0 +1,12 @@ +class GuardedLocalCache { + private Collection localResources; + // A finalizer guardian, which performs the finalize actions for 'GuardedLocalCache' + // even if a subclass does not call 'super.finalize' in its 'finalize' method + private Object finalizerGuardian = new Object() { + protected void finalize() throws Throwable { + for (NativeResource r : localResources) { + r.dispose(); + } + }; + }; +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Frameworks/JUnit/BadSuiteMethod.java b/java/ql/src/Likely Bugs/Frameworks/JUnit/BadSuiteMethod.java new file mode 100644 index 00000000000..2cc578bfff7 --- /dev/null +++ b/java/ql/src/Likely Bugs/Frameworks/JUnit/BadSuiteMethod.java @@ -0,0 +1,21 @@ +public class BadSuiteMethod extends TestCase { + // BAD: JUnit 3.8 does not detect the following method as a 'suite' method. + // The method should be public, static, and return 'junit.framework.Test' + // or one of its subtypes. + static Test suite() { + TestSuite suite = new TestSuite(); + suite.addTest(new MyTests("testEquals")); + suite.addTest(new MyTests("testNotEquals")); + return suite; + } +} + +public class CorrectSuiteMethod extends TestCase { + // GOOD: JUnit 3.8 correctly detects the following method as a 'suite' method. + public static Test suite() { + TestSuite suite = new TestSuite(); + suite.addTest(new MyTests("testEquals")); + suite.addTest(new MyTests("testNotEquals")); + return suite; + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Frameworks/JUnit/BadSuiteMethod.qhelp b/java/ql/src/Likely Bugs/Frameworks/JUnit/BadSuiteMethod.qhelp new file mode 100644 index 00000000000..e89352e5134 --- /dev/null +++ b/java/ql/src/Likely Bugs/Frameworks/JUnit/BadSuiteMethod.qhelp @@ -0,0 +1,41 @@ + + + + + +

    +JUnit 3.8 requires that a suite method for defining a TestSuite that will +be used by a TestRunner has a specific signature. If the suite +method does not have the expected signature, JUnit does not detect the method as a +suite method.

    + +
    + +

    +Make sure that suite methods in junit TestCase classes are declared both public and +static, and that they have a return type of junit.framework.Test or one of its subtypes. +

    + +
    + + +

    In the following example, BadSuiteMethod.suite is not detected by JUnit because it is +not declared public. However, CorrectSuiteMethod.suite is detected by JUnit +because it has the expected signature.

    + + + +
    + + + +
  • +JUnit: JUnit Cookbook. +
  • + + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Frameworks/JUnit/BadSuiteMethod.ql b/java/ql/src/Likely Bugs/Frameworks/JUnit/BadSuiteMethod.ql new file mode 100644 index 00000000000..d1c9ca2ce7b --- /dev/null +++ b/java/ql/src/Likely Bugs/Frameworks/JUnit/BadSuiteMethod.ql @@ -0,0 +1,25 @@ +/** + * @name Bad suite method + * @description A 'suite' method in a JUnit 3.8 test that does not match the expected signature is not + * detected by JUnit. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/wrong-junit-suite-signature + * @tags testability + * maintainability + * frameworks/junit + */ +import java + +from TypeJUnitTestCase junitTestCase, TypeJUnitTest junitTest, Method m +where + m.hasName("suite") and + m.hasNoParameters() and + m.getDeclaringType().hasSupertype+(junitTestCase) and + ( + not m.isPublic() or + not m.isStatic() or + not m.getReturnType().(RefType).getASupertype*() = junitTest + ) +select m, "Bad declaration for suite method." diff --git a/java/ql/src/Likely Bugs/Frameworks/JUnit/TearDownNoSuper.java b/java/ql/src/Likely Bugs/Frameworks/JUnit/TearDownNoSuper.java new file mode 100644 index 00000000000..d05d4f16326 --- /dev/null +++ b/java/ql/src/Likely Bugs/Frameworks/JUnit/TearDownNoSuper.java @@ -0,0 +1,65 @@ +// Abstract class that initializes then shuts down the +// framework after each set of tests +abstract class FrameworkTestCase extends TestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + Framework.init(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + Framework.shutdown(); + } +} + +// The following classes extend 'FrameworkTestCase' to reuse the +// 'setUp' and 'tearDown' methods of the framework. + +public class TearDownNoSuper extends FrameworkTestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + public void testFramework() { + //... + } + + public void testFramework2() { + //... + } + + @Override + protected void tearDown() throws Exception { + // BAD: Does not call 'super.tearDown'. May cause later tests to fail + // when they try to re-initialize an already initialized framework. + // Even if the framework allows re-initialization, it may maintain the + // internal state, which could affect the results of succeeding tests. + System.out.println("Tests complete"); + } +} + +public class TearDownSuper extends FrameworkTestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + public void testFramework() { + //... + } + + public void testFramework2() { + //... + } + + @Override + protected void tearDown() throws Exception { + // GOOD: Correctly calls 'super.tearDown' to shut down the + // framework. + System.out.println("Tests complete"); + super.tearDown(); + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Frameworks/JUnit/TearDownNoSuper.qhelp b/java/ql/src/Likely Bugs/Frameworks/JUnit/TearDownNoSuper.qhelp new file mode 100644 index 00000000000..3bbf3d4855a --- /dev/null +++ b/java/ql/src/Likely Bugs/Frameworks/JUnit/TearDownNoSuper.qhelp @@ -0,0 +1,40 @@ + + + + + +

    +A JUnit 3.8 test method that overrides a non-empty tearDown method should call super.tearDown to make sure that +the superclass performs its de-initialization routines. Not calling tearDown may result in test failures in subsequent tests, or +allow the current state to persist and affect any following tests. +

    + +
    + +

    +Call super.tearDown at the end of the overriding tearDown method. +

    + +
    + + +

    In the following example, TearDownNoSuper.tearDown does not call super.tearDown, +which may cause subsequent tests to fail, or allow the internal state to be maintained. However, TearDownSuper.tearDown +does call super.tearDown, at the end of the method, to enable +FrameworkTestCase.tearDown to perform de-initialization.

    + + + +
    + + + +
  • +JUnit: JUnit Cookbook. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Frameworks/JUnit/TearDownNoSuper.ql b/java/ql/src/Likely Bugs/Frameworks/JUnit/TearDownNoSuper.ql new file mode 100644 index 00000000000..06e5f88220f --- /dev/null +++ b/java/ql/src/Likely Bugs/Frameworks/JUnit/TearDownNoSuper.ql @@ -0,0 +1,21 @@ +/** + * @name TestCase implements tearDown but doesn't call super.tearDown() + * @description A JUnit 3.8 test method that overrides 'tearDown' but does not call 'super.tearDown' + * may result in subsequent tests failing, or allow the current state to persist and + * affect subsequent tests. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/junit-teardown-without-super + * @tags testability + * maintainability + * frameworks/junit + */ +import java + +from TearDownMethod m1 +where + m1.fromSource() and + not m1.getDeclaringType().isAbstract() and + not exists(TearDownMethod m2 | m1.overrides(m2) and m1.callsSuper(m2)) +select m1, "TestCase implements tearDown but doesn't call super.tearDown()." diff --git a/java/ql/src/Likely Bugs/Frameworks/JUnit/TestCaseNoTests.java b/java/ql/src/Likely Bugs/Frameworks/JUnit/TestCaseNoTests.java new file mode 100644 index 00000000000..d9ea2cb651d --- /dev/null +++ b/java/ql/src/Likely Bugs/Frameworks/JUnit/TestCaseNoTests.java @@ -0,0 +1,28 @@ +// BAD: This test case class does not have any valid JUnit 3.8 test methods. +public class TestCaseNoTests38 extends TestCase { + // This is not a test case because it does not start with 'test'. + public void simpleTest() { + //... + } + + // This is not a test case because it takes two parameters. + public void testNotEquals(int i, int j) { + assertEquals(i != j, true); + } + + // This is recognized as a test, but causes JUnit to fail + // when run because it is not public. + void testEquals() { + //... + } +} + +// GOOD: This test case class correctly declares test methods. +public class MyTests extends TestCase { + public void testEquals() { + assertEquals(1, 1); + } + public void testNotEquals() { + assertFalse(1 == 2); + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Frameworks/JUnit/TestCaseNoTests.qhelp b/java/ql/src/Likely Bugs/Frameworks/JUnit/TestCaseNoTests.qhelp new file mode 100644 index 00000000000..770c3a46c68 --- /dev/null +++ b/java/ql/src/Likely Bugs/Frameworks/JUnit/TestCaseNoTests.qhelp @@ -0,0 +1,53 @@ + + + + + +

    +A JUnit 3.8 test case class (that is, a non-abstract class that is a subtype of junit.framework.TestCase) +should contain test methods, and each method must have the correct signature to be used by JUnit. +Otherwise, the JUnit test runner will fail with an error message. +

    + +
    + +

    +Ensure that the test case class contains some test methods, and that each method is of the form: +

    + +
    +public void testXXX() {
    +  // ...
    +}
    +
    + +

    Note that the method name must start with test and the method must not take any +parameters.

    + +

    If the test case class is intended strictly for subclassing by other test case classes, consider +making it abstract to document this intention. It will then no longer be flagged by this query.

    + +

    This rule applies only to JUnit 3.8-style test case classes. In JUnit 4, it is no longer required +to extend junit.framework.TestCase to mark test methods.

    + +
    + + +

    In the following example, TestCaseNoTests38 does not contain any valid JUnit test +methods. However, MyTests contains two valid JUnit test methods.

    + + + +
    + + + +
  • +JUnit: JUnit Cookbook. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Frameworks/JUnit/TestCaseNoTests.ql b/java/ql/src/Likely Bugs/Frameworks/JUnit/TestCaseNoTests.ql new file mode 100644 index 00000000000..364d4d969d9 --- /dev/null +++ b/java/ql/src/Likely Bugs/Frameworks/JUnit/TestCaseNoTests.ql @@ -0,0 +1,32 @@ +/** + * @name Test case has no tests + * @description A test case class whose test methods are not recognized by JUnit 3.8 as valid + * declarations is not used. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/test-case-without-tests + * @tags testability + * maintainability + * frameworks/junit + */ +import java + +// `suite()` methods in `TestCase`s also count as test methods. +class SuiteMethod extends Method { + SuiteMethod() { + this.getDeclaringType() instanceof JUnit38TestClass and + this.isPublic() and + this.isStatic() and + this.hasNoParameters() + } +} + +from JUnit38TestClass j +where + j.fromSource() and + not j.getAnAnnotation().getType().hasQualifiedName("org.junit", "Ignore") and + not j.isAbstract() and + not exists(TestMethod t | t.getDeclaringType() = j) and + not exists(SuiteMethod s | s.getDeclaringType() = j) +select j, "TestCase has no tests." diff --git a/java/ql/src/Likely Bugs/Frameworks/Swing/BadlyOverriddenAdapter.java b/java/ql/src/Likely Bugs/Frameworks/Swing/BadlyOverriddenAdapter.java new file mode 100644 index 00000000000..acac3a909ae --- /dev/null +++ b/java/ql/src/Likely Bugs/Frameworks/Swing/BadlyOverriddenAdapter.java @@ -0,0 +1,5 @@ +add(new MouseAdapter() { + public void mouseClickd(MouseEvent e) { + // ... + } +}); \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Frameworks/Swing/BadlyOverriddenAdapter.qhelp b/java/ql/src/Likely Bugs/Frameworks/Swing/BadlyOverriddenAdapter.qhelp new file mode 100644 index 00000000000..7329123073a --- /dev/null +++ b/java/ql/src/Likely Bugs/Frameworks/Swing/BadlyOverriddenAdapter.qhelp @@ -0,0 +1,97 @@ + + + + + +

    +Event adapters in Swing (and Abstract Window Toolkit) provide a convenient way for programmers to +implement event listeners. However, care must be taken +to get the names of the overridden methods right, or the event handlers will +not be called. +

    + +
    +
    + +

    +The event listener interfaces in Swing (and Abstract Window Toolkit) have many +methods. For example, java.awt.event.MouseListener is defined as +follows: +

    + +public interface MouseListener extends EventListener { + public abstract void mouseClicked(MouseEvent); + public abstract void mousePressed(MouseEvent); + public abstract void mouseReleased(MouseEvent); + public abstract void mouseEntered(MouseEvent); + public abstract void mouseExited(MouseEvent); +} + +

    +The large number of methods can make such interfaces lengthy and +tedious to implement, especially because it is rare that all of the +methods need to be overridden. It is much more +common that you need to override only one method, for example the mouseClicked +event. +

    + +

    +For this reason, Swing supplies adapter classes +that provide default, blank implementations of interface methods. An example +is MouseAdapter, which provides default implementations +for the methods in MouseListener, MouseWheelListener +and MouseMotionListener. (Note that an adapter often implements +multiple interfaces to avoid a large number of small adapter classes.) This +makes it easy for programmers to implement just the methods they need from a +given interface.

    + +

    +Unfortunately, adapter classes are also a source of potential defects. Because the +@Override annotation is not compulsory, it is very easy for programmers not to +use it and then mistype the name of the method. This introduces a new +method rather than implementing the relevant event handler.

    + +
    + + +

    +Ensure that any overriding methods have exactly the same name as the overridden method. +

    + +
    + + +

    In the following example, the programmer has tried to implement the mouseClicked +function but has misspelled the function name. This makes the function inoperable but the programmer +gets no warning about this from the compiler.

    + + + +

    +In the following modified example, the function name is spelled correctly. It is also preceded by the +@Override annotation, which will cause the compiler to display an error if there is +not a function of the same name to be overridden.

    + + + +
    + + + +
  • +D. Flanagan, Java Foundation Classes in a Nutshell, Chapter 26. O'Reilly, 1999. +
  • +
  • +Java Platform, Standard Edition 7, API Specification: +Annotation Type Override. +
  • +
  • +The Java Tutorials: +Event Adapters. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Frameworks/Swing/BadlyOverriddenAdapter.ql b/java/ql/src/Likely Bugs/Frameworks/Swing/BadlyOverriddenAdapter.ql new file mode 100644 index 00000000000..5f8ac8f20bb --- /dev/null +++ b/java/ql/src/Likely Bugs/Frameworks/Swing/BadlyOverriddenAdapter.ql @@ -0,0 +1,39 @@ +/** + * @name Bad implementation of an event Adapter + * @description In a class that extends a Swing or Abstract Window Toolkit event adapter, an + * event handler that does not have exactly the same name as the event handler that it + * overrides means that the overridden event handler is not called. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/wrong-swing-event-adapter-signature + * @tags reliability + * maintainability + * frameworks/swing + */ +import java + +class Adapter extends Class { + Adapter() { + this.getName().matches("%Adapter") and + ( + this.getPackage().hasName("java.awt.event") or + this.getPackage().hasName("javax.swing.event") + ) + } +} + +from Class c, Adapter adapter, Method m +where + adapter = c.getASupertype() and + c = m.getDeclaringType() and + exists(Method original | adapter = original.getDeclaringType() | + m.getName() = original.getName() + ) and + not exists(Method overridden | adapter = overridden.getDeclaringType() | + m.overrides(overridden) + ) and + // The method is not used for any other purpose. + not exists(MethodAccess ma | ma.getMethod() = m) +select m, "Method " + m.getName() + " attempts to override a method in " + adapter.getName() + + ", but does not have the same argument types. " + m.getName() + " will not be called when an event occurs." diff --git a/java/ql/src/Likely Bugs/Frameworks/Swing/BadlyOverriddenAdapterGood.java b/java/ql/src/Likely Bugs/Frameworks/Swing/BadlyOverriddenAdapterGood.java new file mode 100644 index 00000000000..2e89bee6b84 --- /dev/null +++ b/java/ql/src/Likely Bugs/Frameworks/Swing/BadlyOverriddenAdapterGood.java @@ -0,0 +1,6 @@ +add(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + // ... + } +}); \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Frameworks/Swing/ThreadSafety.java b/java/ql/src/Likely Bugs/Frameworks/Swing/ThreadSafety.java new file mode 100644 index 00000000000..b7aeb2fe6ac --- /dev/null +++ b/java/ql/src/Likely Bugs/Frameworks/Swing/ThreadSafety.java @@ -0,0 +1,19 @@ +class MyFrame extends JFrame { + public MyFrame() { + setSize(640, 480); + setTitle("BrokenSwing"); + } +} + +public class BrokenSwing { + private static void doStuff(MyFrame frame) { + // BAD: Direct call to a Swing component after it has been realized + frame.setTitle("Title"); + } + + public static void main(String[] args) { + MyFrame frame = new MyFrame(); + frame.setVisible(true); + doStuff(frame); + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Frameworks/Swing/ThreadSafety.qhelp b/java/ql/src/Likely Bugs/Frameworks/Swing/ThreadSafety.qhelp new file mode 100644 index 00000000000..3da88fbcb4f --- /dev/null +++ b/java/ql/src/Likely Bugs/Frameworks/Swing/ThreadSafety.qhelp @@ -0,0 +1,110 @@ + + + + + +

    +Because Swing components are not thread-safe (that is, they do not support concurrent access from multiple +threads), Swing has a rule that states that method calls on Swing components that have been +realized (see below) must be made from a special thread known as the +event-dispatching thread. Failure to observe this rule may result in multiple threads +accessing a Swing component concurrently, with the potential for deadlocks, race conditions and other +errors related to multi-threading.

    + +

    A component is considered realized if its paint method has been, or could +be, called at this point. Realization is triggered according to the following rules: +

    + +
      +
    • +A top-level window is realized if setVisible(true), +show or pack is called on it. +
    • +
    • +Realizing a container realizes the components it contains. +
    • +
    + +

    +There are a few exceptions to the rule. These are documented +more fully in [The Swing Connection] but the key exceptions are: +

    + +
      +
    • +It is safe to call the repaint, revalidate and +invalidate methods on a Swing component from any thread. +
    • +
    • +It is safe to add and remove listeners from any thread. Therefore, any method of the +form add*Listener or remove*Listener is thread-safe. +
    • +
    + +
    + + +

    +Ensure that method calls on Swing components are made from the event-dispatching thread. If you need +to call a method on a Swing component from another thread, you must do so using the event-dispatching +thread. Swing provides a SwingUtilities class that you can use to ask the +event-dispatching thread to run arbitrary code on your components, by calling one of +two methods. Each method takes a Runnable as its only argument: +

    + +
      +
    • +SwingUtilities.invokeLater asks the event-dispatching thread to +run some code and then immediately returns (that is, it is non-blocking). The code +is run at some indeterminate time in the future, but the thread that calls +invokeLater does not wait for it. +
    • +
    • +SwingUtilities.invokeAndWait asks the event-dispatching thread to +run some code and then waits for it to complete (that is, it is blocking). +
    • +
    + +
    + + +

    +In the following example, there is a call from the main thread to a method, setTitle, on the +MyFrame object after the object has been realized by the setVisible(true) call. +This represents an unsafe call to a Swing method from a thread other than the +event-dispatching thread. +

    + + + +

    In the following modified example, the call to setTitle is instead called from +within a call to invokeLater. +

    + + + +
    + + + +
  • +D. Flanagan, Java Foundation Classes in a Nutshell, p.28. O'Reilly, 1999. +
  • +
  • +Java Developer's Journal: +Building Thread-Safe GUIs with Swing. +
  • +
  • +The Java Tutorials: +Concurrency in Swing. +
  • +
  • +The Swing Connection: +Threads and Swing. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Frameworks/Swing/ThreadSafety.ql b/java/ql/src/Likely Bugs/Frameworks/Swing/ThreadSafety.ql new file mode 100644 index 00000000000..9ffa8cfcaa7 --- /dev/null +++ b/java/ql/src/Likely Bugs/Frameworks/Swing/ThreadSafety.ql @@ -0,0 +1,28 @@ +/** + * @name Thread safety + * @description Calling Swing methods from a thread other than the event-dispatching thread may + * result in multi-threading errors. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/swing-thread-safety + * @tags reliability + * maintainability + * frameworks/swing + */ + +import java + +from MethodAccess ma, Method m, MainMethod main +where + ma.getQualifier().getType().getCompilationUnit().getPackage().getName() + .matches("javax.swing%") and + ( + m.hasName("show") and m.hasNoParameters() or + m.hasName("pack") and m.hasNoParameters() or + m.hasName("setVisible") and m.getNumberOfParameters() = 1 + ) and + ma.getMethod() = m and + ma.getEnclosingCallable() = main +select ma, "Call to swing method in " + main.getDeclaringType().getName() + + " needs to be performed in Swing event thread." diff --git a/java/ql/src/Likely Bugs/Frameworks/Swing/ThreadSafetyGood.java b/java/ql/src/Likely Bugs/Frameworks/Swing/ThreadSafetyGood.java new file mode 100644 index 00000000000..a47729d637e --- /dev/null +++ b/java/ql/src/Likely Bugs/Frameworks/Swing/ThreadSafetyGood.java @@ -0,0 +1,24 @@ +class MyFrame extends JFrame { + public MyFrame() { + setSize(640, 480); + setTitle("BrokenSwing"); + } +} + +public class GoodSwing { + private static void doStuff(final MyFrame frame) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + // GOOD: Call to Swing component made via the + // event-dispatching thread using 'invokeLater' + frame.setTitle("Title"); + } + }); + } + + public static void main(String[] args) { + MyFrame frame = new MyFrame(); + frame.setVisible(true); + doStuff(frame); + } +} diff --git a/java/ql/src/Likely Bugs/I18N/MissingLocaleArgument.java b/java/ql/src/Likely Bugs/I18N/MissingLocaleArgument.java new file mode 100644 index 00000000000..741bc708905 --- /dev/null +++ b/java/ql/src/Likely Bugs/I18N/MissingLocaleArgument.java @@ -0,0 +1,14 @@ +public static void main(String args[]) { + String phrase = "I miss my home in Mississippi."; + + // AVOID: Calling 'toLowerCase()' or 'toUpperCase()' + // produces different results depending on what the default locale is. + System.out.println(phrase.toUpperCase()); + System.out.println(phrase.toLowerCase()); + + // GOOD: Explicitly setting the locale when calling 'toLowerCase()' or + // 'toUpperCase()' ensures that the resulting string is + // English, regardless of the default locale. + System.out.println(phrase.toLowerCase(Locale.ENGLISH)); + System.out.println(phrase.toUpperCase(Locale.ENGLISH)); +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/I18N/MissingLocaleArgument.qhelp b/java/ql/src/Likely Bugs/I18N/MissingLocaleArgument.qhelp new file mode 100644 index 00000000000..68ef88f814b --- /dev/null +++ b/java/ql/src/Likely Bugs/I18N/MissingLocaleArgument.qhelp @@ -0,0 +1,51 @@ + + + + + +

    +The parameterless versions of String.toUpperCase() and String.toLowerCase() use the default locale of the Java Virtual Machine when +transforming strings. This can cause unexpected behavior for certain locales. +

    + +
    + +

    Use the corresponding methods with explicit locale parameters to ensure that the results are consistent +across all locales. For example:

    + +

    System.out.println("I".toLowerCase(java.util.Locale.ENGLISH));

    + +

    prints i, regardless of the default locale.

    + +
    + + +

    In the following example, the calls to the parameterless functions may return different strings +for different locales. For example, if the default locale is ENGLISH, the function toLowerCase() +converts a capital I to i; if the default locale is TURKISH, the function +toLowerCase() converts a capital I to the Unicode Character "Latin small +letter dotless i" (U+0131) (Turkish HTML +Codes, Unicode Hexadecimal & HTML Names). +

    + +

    To ensure that an English string is returned, regardless of the default locale, the example shows +how to call toLowerCase and pass locale.ENGLISH as the argument. (This +assumes that the text is English. If the text is Turkish, you should pass +locale.TURKISH as the argument.)

    + + + +
    + + + +
  • +Java API Documentation: +String.toUpperCase(). +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/I18N/MissingLocaleArgument.ql b/java/ql/src/Likely Bugs/I18N/MissingLocaleArgument.ql new file mode 100644 index 00000000000..08d437435a1 --- /dev/null +++ b/java/ql/src/Likely Bugs/I18N/MissingLocaleArgument.ql @@ -0,0 +1,23 @@ +/** + * @name Missing Locale parameter to toUpperCase() or toLowerCase() + * @description Calling 'String.toUpperCase()' or 'String.toLowerCase()' without specifying the + * locale may cause unexpected results for certain default locales. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/missing-locale-argument + * @tags reliability + * maintainability + */ +import java + +from MethodAccess ma, Method changecase +where + ( + changecase.hasName("toUpperCase") or + changecase.hasName("toLowerCase") + ) and + changecase.hasNoParameters() and + changecase.getDeclaringType() instanceof TypeString and + ma.getMethod() = changecase +select ma, changecase.getName() + " without locale parameter." diff --git a/java/ql/src/Likely Bugs/Inheritance/NoNonFinalInConstructor.java b/java/ql/src/Likely Bugs/Inheritance/NoNonFinalInConstructor.java new file mode 100644 index 00000000000..23f557b6497 --- /dev/null +++ b/java/ql/src/Likely Bugs/Inheritance/NoNonFinalInConstructor.java @@ -0,0 +1,22 @@ +public class Super { + public Super() { + init(); + } + + public void init() { + } +} + +public class Sub extends Super { + String s; + int length; + + public Sub(String s) { + this.s = s==null ? "" : s; + } + + @Override + public void init() { + length = s.length(); + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Inheritance/NoNonFinalInConstructor.qhelp b/java/ql/src/Likely Bugs/Inheritance/NoNonFinalInConstructor.qhelp new file mode 100644 index 00000000000..cca1d2f7d31 --- /dev/null +++ b/java/ql/src/Likely Bugs/Inheritance/NoNonFinalInConstructor.qhelp @@ -0,0 +1,57 @@ + + + + + +

    If a constructor calls a method that is overridden in a subclass, +it can cause the overriding method in the subclass +to be called before the subclass has been initialized. This can lead to unexpected results. +

    + +
    + + +

    Do not call a non-final method from within a constructor if that method +could be overridden in a subclass.

    + +
    + + +

    +In the following example, executing new Sub("test") results in a NullPointerException. +This is because the subclass constructor implicitly calls the superclass constructor, which in turn +calls the overridden init method before the field s is initialized in the subclass constructor. +

    + + + +

    +To avoid this problem: +

    + +
      +
    • The init method in the super constructor should be made final or private.
    • +
    • The initialization that is performed in the overridden init method in the subclass + can be moved to the subclass constructor itself, or delegated to a separate final + or private method that is called from within the subclass constructor.
    • +
    + +
    + + + +
  • + J. Bloch, + Effective Java (second edition), pp. 89–90. + Addison-Wesley, 2008. +
  • +
  • + The Java Tutorials: + Writing Final Classes and Methods. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Inheritance/NoNonFinalInConstructor.ql b/java/ql/src/Likely Bugs/Inheritance/NoNonFinalInConstructor.ql new file mode 100644 index 00000000000..e13c8e99904 --- /dev/null +++ b/java/ql/src/Likely Bugs/Inheritance/NoNonFinalInConstructor.ql @@ -0,0 +1,68 @@ +/** + * @name Non-final method invocation in constructor + * @description If a constructor calls a method that is overridden in a subclass, the result can be + * unpredictable. + * @kind problem + * @problem.severity error + * @precision very-high + * @id java/non-final-call-in-constructor + * @tags reliability + * correctness + * logic + */ +import java + +private +predicate writtenInOneCallable(Field f) { + strictcount(Callable m | m.writes(f)) = 1 +} + +private +FieldWrite fieldWriteOnlyIn(Callable m, Field f) { + result.getField() = f and + m.writes(f) and + writtenInOneCallable(f) +} + +private +FieldRead nonFinalFieldRead(Callable m, Field f) { + result.getField() = f and + result.getEnclosingCallable() = m and + not f.isFinal() +} + +private +MethodAccess unqualifiedCallToNonAbstractMethod(Constructor c, Method m) { + result.getEnclosingCallable() = c and + (not exists(result.getQualifier()) or + result.getQualifier().(ThisAccess).getType() = c.getDeclaringType()) and + m = result.getMethod() and + not m.isAbstract() +} + +from Constructor c, MethodAccess ma, Method m, Method n, Field f, FieldRead fa, Constructor d, FieldWrite fw +where + // Method access in a constructor + // which is an access to the object being initialized, ... + ma = unqualifiedCallToNonAbstractMethod(c, m) and + // ... there exists an overriding method in a subtype, + n.overrides(m) and + n.getDeclaringType().getASupertype+() = c.getDeclaringType() and + // ... the method is in a supertype of c, + m.getDeclaringType() = c.getDeclaringType().getASupertype*() and + // ... `n` reads a non-final field `f`, + fa = nonFinalFieldRead(n, f) and + // ... which is declared in a subtype of `c`, + f.getDeclaringType().getASupertype+() = c.getDeclaringType() and + // ... `f` is written only in the subtype constructor, and + fw = fieldWriteOnlyIn(d, f) and + // ... the subtype constructor calls (possibly indirectly) the offending super constructor. + d.callsConstructor+(c) +select + ma, "One $@ $@ a $@ that is only $@ in the $@, so it is uninitialized in this $@.", + n, "overriding implementation", + fa, "reads", + f, "subclass field", + fw, "initialized", + d, "subclass constructor", + c, "super constructor" diff --git a/java/ql/src/Likely Bugs/Likely Typos/ConstructorTypo.java b/java/ql/src/Likely Bugs/Likely Typos/ConstructorTypo.java new file mode 100644 index 00000000000..3fda2172487 --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/ConstructorTypo.java @@ -0,0 +1,10 @@ +class MasterSingleton +{ + // ... + + private static MasterSingleton singleton = new MasterSingleton(); + public static MasterSingleton getInstance() { return singleton; } + + // Make the constructor 'protected' to prevent this class from being instantiated. + protected void MasterSingleton() { } +} diff --git a/java/ql/src/Likely Bugs/Likely Typos/ConstructorTypo.qhelp b/java/ql/src/Likely Bugs/Likely Typos/ConstructorTypo.qhelp new file mode 100644 index 00000000000..2d0aa9af1b2 --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/ConstructorTypo.qhelp @@ -0,0 +1,55 @@ + + + + + +

    A method that has the same name as its declaring type may be intended to be a constructor, not a +method.

    + +
    + + +

    The following example shows how the singleton design pattern is often misimplemented. The +programmer intends the constructor of MasterSingleton to +be protected so that it cannot be instantiated (because the +singleton instance should be retrieved using getInstance). +However, the programmer accidentally wrote void in front of the +constructor name, which makes it a method rather than a constructor.

    + + + +
    + + +

    Ensure that methods that have the same name as their declaring type are intended to be +methods. Even if they are intended to be methods, it may be better to rename them to avoid +confusion.

    + +
    + + + +
  • + J. Bloch and N. Gafter, Java Puzzlers: Traps, Pitfalls, and Corner Cases, Puzzle 63. + Addison-Wesley, 2005. +
  • +
  • +E. Gamma, R. Helm, R. Johnson, J. Vlissides, +Design Patterns: Elements of Reusable Objection-Oriented Software, +§3. Addison-Wesley Longman Publishing Co. Inc., 1995. +
  • +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • +
  • +Java Language Specification: +8.4 Method Declarations, +8.8 Constructor Declarations. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Likely Typos/ConstructorTypo.ql b/java/ql/src/Likely Bugs/Likely Typos/ConstructorTypo.ql new file mode 100644 index 00000000000..7656d061f5f --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/ConstructorTypo.ql @@ -0,0 +1,20 @@ +/** + * @name Typo in constructor + * @description A method that has the same name as its declaring type may have been intended to be + * a constructor. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/constructor-typo + * @tags maintainability + * readability + * naming + */ +import java + +from Method m +where + m.hasName(m.getDeclaringType().getName()) and + m.fromSource() +select m, "This method has the same name as its declaring class." + + " Should it be a constructor?" diff --git a/java/ql/src/Likely Bugs/Likely Typos/ContainerSizeCmpZero.java b/java/ql/src/Likely Bugs/Likely Typos/ContainerSizeCmpZero.java new file mode 100644 index 00000000000..f69824bf858 --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/ContainerSizeCmpZero.java @@ -0,0 +1,11 @@ +import java.io.File; + +class ContainerSizeCmpZero +{ + private static File MakeFile(String filename) { + if(filename != null && filename.length() >= 0) { + return new File(filename); + } + return new File("default.name"); + } +} diff --git a/java/ql/src/Likely Bugs/Likely Typos/ContainerSizeCmpZero.qhelp b/java/ql/src/Likely Bugs/Likely Typos/ContainerSizeCmpZero.qhelp new file mode 100644 index 00000000000..712b94cbaeb --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/ContainerSizeCmpZero.qhelp @@ -0,0 +1,45 @@ + + + + + +

    A map, collection, string or array will always have size of at least zero. Checking that an object of one of these types has size greater than or equal to zero will always be true, while checking that it has size less than zero will always be false.

    + +
    + + +

    For collections, maps and strings, if the intention was to check whether the object was empty, is it preferred to use the isEmpty() method. For arrays, check that the length field is greater than (not equal to) zero. +

    + +
    + + +

    The following example shows creation of a file guarded by comparison of a string length with zero. This can result in the attempted creation of a file with an empty name.

    + + + +

    In the following revised example, the check against zero has been replaced with a call to isEmpty(). This correctly guards against the attempted creation of a file with an empty name.

    + + + +
    + + + +
  • +Java 2 Platform, Standard Edition 6.0, API Specification: + + +Collection.isEmpty(), + + +Map.isEmpty(), + + +String.isEmpty(). +
  • + +
    +
    diff --git a/java/ql/src/Likely Bugs/Likely Typos/ContainerSizeCmpZero.ql b/java/ql/src/Likely Bugs/Likely Typos/ContainerSizeCmpZero.ql new file mode 100644 index 00000000000..304b32b05ff --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/ContainerSizeCmpZero.ql @@ -0,0 +1,84 @@ +/** + * @name Container size compared to zero + * @description Comparing the size of a container to zero with this operator will always return the same value. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id java/test-for-negative-container-size + * @tags reliability + * correctness + * logic + */ + +import java +import semmle.code.java.Collections +import semmle.code.java.Maps + +/** A union of the possible kinds of container size calls. */ +abstract class SizeOfContainer extends Expr { + abstract string getContainerKind(); +} + +/** A read access to the `length` field of an array. */ +class ArrayLengthRead extends FieldRead, SizeOfContainer { + ArrayLengthRead() { + this.getField() instanceof ArrayLengthField + } + + override string getContainerKind() { result = "an array" } +} + +/** An access to `String.length()`. */ +class StringLengthRead extends MethodAccess, SizeOfContainer { + StringLengthRead() { + this.getMethod() instanceof StringLengthMethod + } + + override string getContainerKind() { result = "a string" } +} + +/** An access to `Collection.size()`. */ +class CollectionSizeCall extends MethodAccess, SizeOfContainer { + CollectionSizeCall() { + this.getMethod() instanceof CollectionSizeMethod + } + + override string getContainerKind() { result = "a collection" } +} + +/** An access to `Map.size()`. */ +class MapSizeCall extends MethodAccess, SizeOfContainer { + MapSizeCall() { + this.getMethod() instanceof MapSizeMethod + } + + override string getContainerKind() { result = "a map" } +} + +class IntegralZeroLiteral extends Literal { + IntegralZeroLiteral() { + (this instanceof IntegerLiteral or this instanceof LongLiteral) and + this.getValue().toInt() = 0 + } +} + +private predicate comparisonOfContainerSizeToZero(BinaryExpr e, string containerKind, string trueOrFalse) { + exists(Expr l, Expr r | l = e.getLeftOperand() and r = e.getRightOperand() | + (e instanceof LTExpr and l.(SizeOfContainer).getContainerKind() = containerKind and + r instanceof IntegralZeroLiteral and trueOrFalse = "false") + or + (e instanceof GTExpr and l instanceof IntegralZeroLiteral and + r.(SizeOfContainer).getContainerKind() = containerKind and trueOrFalse = "false") + or + (e instanceof GEExpr and l.(SizeOfContainer).getContainerKind() = containerKind and + r instanceof IntegralZeroLiteral and trueOrFalse = "true") + or + (e instanceof LEExpr and l instanceof IntegralZeroLiteral and + r.(SizeOfContainer).getContainerKind() = containerKind and trueOrFalse = "true") + ) +} + +from BinaryExpr e, string containerKind, string trueOrFalse +where comparisonOfContainerSizeToZero(e, containerKind, trueOrFalse) +select e, "This expression is always " + trueOrFalse + ", since " + + containerKind + " can never have negative size." diff --git a/java/ql/src/Likely Bugs/Likely Typos/ContainerSizeCmpZeroGood.java b/java/ql/src/Likely Bugs/Likely Typos/ContainerSizeCmpZeroGood.java new file mode 100644 index 00000000000..b729fcbc8eb --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/ContainerSizeCmpZeroGood.java @@ -0,0 +1,11 @@ +import java.io.File; + +class ContainerSizeCmpZero +{ + private static File MakeFile(String filename) { + if(filename != null && !filename.isEmpty()) { + return new File(filename); + } + return new File("default.name"); + } +} diff --git a/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.java b/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.java new file mode 100644 index 00000000000..17a31a680b1 --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.java @@ -0,0 +1,9 @@ +String getKind(Animal a) { + if (a instanceof Mammal) { + return "Mammal"; + } else if (a instanceof Tiger) { + return "Tiger!"; + } else { + return "unknown"; + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.qhelp b/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.qhelp new file mode 100644 index 00000000000..bf89dca5870 --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.qhelp @@ -0,0 +1,55 @@ + + + + + +

    +If an instanceof expression occurs in a position where the +type test is guaranteed to return false, this is often due +to a typo or logical mistake. It also suggests that the surrounding code +is not well tested, or possibly even dead. +

    + +

    +Similarly, a cast that is guaranteed to fail usually indicates badly +tested or dead code. +

    + +
    + + +

    +Inspect the surrounding code for logical errors. +

    + +
    + + +

    +In the following example, method getKind first checks whether +its argument x is an instance of class Mammal, +and then whether it is an instance of class Tiger. +

    + + + +

    +If Tiger is a subclass of Mammal, then the second +instanceof check can never evaluate to true. Clearly, +the two conditions should be swapped: +

    + + + +
    + + + +
  • +Java Language Specification: Type Comparison Operator instanceof. +
  • + +
    +
    diff --git a/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.ql b/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.ql new file mode 100644 index 00000000000..95047d4507d --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.ql @@ -0,0 +1,50 @@ +/** + * @name Contradictory type checks + * @description Contradictory dynamic type checks in `instanceof` expressions + * and casts may cause dead code or even runtime errors, and usually + * indicate a logic error. + * @kind problem + * @problem.severity error + * @precision very-high + * @id java/contradictory-type-checks + * @tags correctness + * logic + */ + +import java +import semmle.code.java.controlflow.Guards +import semmle.code.java.dataflow.SSA + +/** `ioe` is of the form `va instanceof t`. */ +predicate instanceOfCheck(InstanceOfExpr ioe, VarAccess va, RefType t) { + ioe.getExpr().getProperExpr() = va and + ioe.getTypeName().getType().(RefType).getSourceDeclaration() = t +} + +/** Expression `e` assumes that `va` could be of type `t`. */ +predicate requiresInstanceOf(Expr e, VarAccess va, RefType t) { + // `e` is a cast of the form `(t)va` + e.(CastExpr).getExpr() = va and t = e.getType().(RefType).getSourceDeclaration() or + // `e` is `va instanceof t` + instanceOfCheck(e, va, t) +} + +/** + * `e` assumes that `v` could be of type `t`, but `cond`, in fact, ensures that + * `v` is not of type `sup`, which is a supertype of `t`. + */ +predicate contradictoryTypeCheck(Expr e, Variable v, RefType t, RefType sup, Expr cond) { + exists(SsaVariable ssa | + ssa.getSourceVariable().getVariable() = v and + requiresInstanceOf(e, ssa.getAUse(), t) and + sup = t.getASupertype*() and + instanceOfCheck(cond, ssa.getAUse(), sup) and + cond.(Guard).controls(e.getBasicBlock(), false) + ) +} + +from Expr e, Variable v, RefType t, RefType sup, Expr cond +where + contradictoryTypeCheck(e, v, t, sup, cond) +select e, "Variable $@ cannot be of type $@ here, since $@ ensures that it is not of type $@.", + v, v.getName(), t, t.getName(), cond, "this expression", sup, sup.getName() diff --git a/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecksGood.java b/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecksGood.java new file mode 100644 index 00000000000..9589393c017 --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecksGood.java @@ -0,0 +1,9 @@ +String getKind(Animal a) { + if (a instanceof Tiger) { + return "Tiger!"; + } else if (a instanceof Mammal) { + return "Mammal"; + } else { + return "unknown"; + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Likely Typos/DangerousNonCircuitLogic.java b/java/ql/src/Likely Bugs/Likely Typos/DangerousNonCircuitLogic.java new file mode 100644 index 00000000000..f0ad5add18f --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/DangerousNonCircuitLogic.java @@ -0,0 +1,15 @@ +public class Person +{ + private String forename; + private String surname; + + public boolean hasForename() { + return forename != null && forename.length() > 0; // GOOD: Conditional-and operator + } + + public boolean hasSurname() { + return surname != null & surname.length() > 0; // BAD: Bitwise AND operator + } + + // ... +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Likely Typos/DangerousNonCircuitLogic.qhelp b/java/ql/src/Likely Bugs/Likely Typos/DangerousNonCircuitLogic.qhelp new file mode 100644 index 00000000000..48d4d37c5ab --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/DangerousNonCircuitLogic.qhelp @@ -0,0 +1,66 @@ + + + + + +

    Using a bitwise logical operator (& or +|) on a boolean where a conditional-and or conditional-or operator +(&& or ||) is intended may yield the wrong result or +cause an exception. This is especially true if the left-hand operand is a guard for the +right-hand operand.

    + +

    Typically, as in the example below, this kind of defect is introduced by +simply mistyping the intended logical operator rather than any conceptual +mistake by the programmer.

    + +
    + + +

    If the right-hand side of an expression is only intended to be +evaluated if the left-hand side evaluates to true, use a +conditional-and.

    + +

    Similarly, if the right-hand side of an expression is only intended +to be evaluated if the left-hand side evaluates to false, use a +conditional-or.

    + +
    + + +

    In the following example, the hasForename method is implemented correctly. For a forename +to be valid it must be a non-null string with a non-zero length. The method has +two expressions (forename != null and forename.length() > +0) to check these two properties. The second check is +executed only if the first succeeds, because they are combined using a conditional-and +operator (&&).

    + +

    In contrast, although hasSurname looks almost the same, it contains +a defect. Again there are two tests (surname != null and +surname.length() > 0), but they are linked by a +bitwise logical operator (&). Both sides of a bitwise logical +operator are always evaluated, so if surname is +null the hasSurname method throws a +NullPointerException. To fix the defect, change & to &&.

    + + + +
    + + + +
  • + J. Bloch and N. Gafter, Java Puzzlers: Traps, Pitfalls, and Corner Cases, Puzzle 42. + Addison-Wesley, 2005. +
  • +
  • +Java Language Specification: +15.22.2 Boolean Logical Operators &, ^, and |, +15.23 Conditional-And Operator &&, +15.24 Conditional-Or Operator ||. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Likely Typos/DangerousNonCircuitLogic.ql b/java/ql/src/Likely Bugs/Likely Typos/DangerousNonCircuitLogic.ql new file mode 100644 index 00000000000..85606732456 --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/DangerousNonCircuitLogic.ql @@ -0,0 +1,41 @@ +/** + * @name Dangerous non-short-circuit logic + * @description Using a bitwise logical operator on a boolean where a conditional-and or + * conditional-or operator is intended may yield the wrong result or + * cause an exception. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/non-short-circuit-evaluation + * @tags reliability + * readability + * external/cwe/cwe-691 + */ +import java + +/** An expression containing a method access, array access, or qualified field access. */ +class DangerousExpression extends Expr { + DangerousExpression() { + exists(Expr e | this = e.getParent*() | + e instanceof MethodAccess or + e instanceof ArrayAccess or + exists(e.(FieldAccess).getQualifier()) + ) + } +} + +/** A use of `&` or `|` on operands of type boolean. */ +class NonShortCircuit extends BinaryExpr { + NonShortCircuit() { + ( + this instanceof AndBitwiseExpr or + this instanceof OrBitwiseExpr + ) and + this.getLeftOperand().getType().hasName("boolean") and + this.getRightOperand().getType().hasName("boolean") and + this.getRightOperand() instanceof DangerousExpression + } +} + +from NonShortCircuit e +select e, "Possibly dangerous use of non-short circuit logic." diff --git a/java/ql/src/Likely Bugs/Likely Typos/EqualsTypo.java b/java/ql/src/Likely Bugs/Likely Typos/EqualsTypo.java new file mode 100644 index 00000000000..5250fd748a9 --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/EqualsTypo.java @@ -0,0 +1,14 @@ +public class Complex +{ + private double real; + private double complex; + + // ... + + public boolean equal(Object obj) { // The method is named 'equal'. + if (!getClass().equals(obj.getClass())) + return false; + Complex other = (Complex) obj; + return real == other.real && complex == other.complex; + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Likely Typos/EqualsTypo.qhelp b/java/ql/src/Likely Bugs/Likely Typos/EqualsTypo.qhelp new file mode 100644 index 00000000000..8d410cc883e --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/EqualsTypo.qhelp @@ -0,0 +1,36 @@ + + + + + +

    A method named equal may be a typographical error. +equals may have been intended instead.

    + +
    + + +

    Ensure that any such method is intended to have this name. Even if it is, it may be better to +rename it to avoid confusion with the inherited method Object.equals.

    + +
    + + +

    The following example shows a method named equal. It may be better to rename it.

    + + + +
    + + + +
  • +Java 2 Platform, Standard Edition 5.0, API Specification: + +equals. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Likely Typos/EqualsTypo.ql b/java/ql/src/Likely Bugs/Likely Typos/EqualsTypo.ql new file mode 100644 index 00000000000..4874ca9a149 --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/EqualsTypo.ql @@ -0,0 +1,20 @@ +/** + * @name Typo in equals + * @description A method named 'equal' may be intended to be named 'equals'. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/equals-typo + * @tags maintainability + * readability + * naming + */ +import java + +from Method equal +where + equal.hasName("equal") and + equal.getNumberOfParameters() = 1 and + equal.getAParamType() instanceof TypeObject and + equal.fromSource() +select equal, "Did you mean to name this method 'equals' rather than 'equal'?" diff --git a/java/ql/src/Likely Bugs/Likely Typos/FormatStringsOverview.qhelp b/java/ql/src/Likely Bugs/Likely Typos/FormatStringsOverview.qhelp new file mode 100644 index 00000000000..b43532dcc6b --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/FormatStringsOverview.qhelp @@ -0,0 +1,24 @@ + + + + +

    +When formatting strings using printf-style format strings, one +must ensure that the number of supplied arguments matches the number of +arguments referenced by the format string. Additional arguments will be thrown +away silently, which may not be the intended behavior, and too few arguments will cause an +IllegalFormatException. +

    +

    +Format strings are used by the format method on the classes +String, Formatter, Console, +PrintWriter, and PrintStream. Several of these +classes also supply the method alias printf. The class +Console has two additional methods, readLine and +readPassword, that also use format strings. +

    +
    + +
    diff --git a/java/ql/src/Likely Bugs/Likely Typos/FormatStringsRefs.qhelp b/java/ql/src/Likely Bugs/Likely Typos/FormatStringsRefs.qhelp new file mode 100644 index 00000000000..99224f670e2 --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/FormatStringsRefs.qhelp @@ -0,0 +1,23 @@ + + + + + +
  • +The Java API Specification: +Format string syntax, +Class String, +Class Formatter, +Class Console, +Class PrintWriter, +Class PrintStream. +
  • +
  • +org.slf4j.Logger. +
  • + +
    + +
    diff --git a/java/ql/src/Likely Bugs/Likely Typos/HashCodeTypo.java b/java/ql/src/Likely Bugs/Likely Typos/HashCodeTypo.java new file mode 100644 index 00000000000..541560c7ab8 --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/HashCodeTypo.java @@ -0,0 +1,14 @@ +public class Person +{ + private String title; + private String forename; + private String surname; + + // ... + + public int hashcode() { // The method is named 'hashcode'. + int hash = 23 * title.hashCode(); + hash ^= 13 * forename.hashCode(); + return hash ^ surname.hashCode(); + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Likely Typos/HashCodeTypo.qhelp b/java/ql/src/Likely Bugs/Likely Typos/HashCodeTypo.qhelp new file mode 100644 index 00000000000..d5fa0d0e74b --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/HashCodeTypo.qhelp @@ -0,0 +1,36 @@ + + + + + +

    A method named hashcode may be a typographical error. hashCode +(different capitalization) may have been intended instead.

    + +
    + + +

    Ensure that any such method is intended to have this name. Even if it is, it may be better to +rename it to avoid confusion with the inherited method Object.hashCode.

    + +
    + + +

    The following example shows a method named hashcode. It may be better to rename it.

    + + + +
    + + + +
  • +Java 2 Platform, Standard Edition 5.0, API Specification: + +hashCode. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Likely Typos/HashCodeTypo.ql b/java/ql/src/Likely Bugs/Likely Typos/HashCodeTypo.ql new file mode 100644 index 00000000000..d43c8cd4e25 --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/HashCodeTypo.ql @@ -0,0 +1,19 @@ +/** + * @name Typo in hashCode + * @description A method named 'hashcode' may be intended to be named 'hashCode'. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/hashcode-typo + * @tags maintainability + * readability + * naming + */ +import java + +from Method m +where + m.hasName("hashcode") and + m.hasNoParameters() and + m.fromSource() +select m, "Should this method be called 'hashCode' rather than 'hashcode'?" diff --git a/java/ql/src/Likely Bugs/Likely Typos/MissingFormatArg.qhelp b/java/ql/src/Likely Bugs/Likely Typos/MissingFormatArg.qhelp new file mode 100644 index 00000000000..07d6a89f019 --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/MissingFormatArg.qhelp @@ -0,0 +1,27 @@ + + + + + + +

    + Supply the correct number of arguments to the format method, or change the format string + to use the correct arguments. +

    +
    + + +

    +The following example supplies only one argument to be formatted, but the +format string refers to two arguments, so this will throw an +IllegalFormatException. +

    + + +
    + + + +
    diff --git a/java/ql/src/Likely Bugs/Likely Typos/MissingFormatArg.ql b/java/ql/src/Likely Bugs/Likely Typos/MissingFormatArg.ql new file mode 100644 index 00000000000..87057f3ed50 --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/MissingFormatArg.ql @@ -0,0 +1,21 @@ +/** + * @name Missing format argument + * @description A format call with an insufficient number of arguments causes + * an 'IllegalFormatException'. + * @kind problem + * @problem.severity error + * @precision very-high + * @id java/missing-format-argument + * @tags correctness + * external/cwe/cwe-685 + */ +import java +import semmle.code.java.StringFormat + +from FormattingCall fmtcall, FormatString fmt, int refs, int args +where + fmtcall.getAFormatString() = fmt and + refs = fmt.getMaxFmtSpecIndex() and + args = fmtcall.getVarargsCount() and + refs > args +select fmtcall, "This format call refers to " + refs + " argument(s) but only supplies " + args + " argument(s)." diff --git a/java/ql/src/Likely Bugs/Likely Typos/MissingFormatArgBad.java b/java/ql/src/Likely Bugs/Likely Typos/MissingFormatArgBad.java new file mode 100644 index 00000000000..0caa8348281 --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/MissingFormatArgBad.java @@ -0,0 +1 @@ +System.out.format("First string: %s Second string: %s", "Hello world"); diff --git a/java/ql/src/Likely Bugs/Likely Typos/MissingSpaceTypo.qhelp b/java/ql/src/Likely Bugs/Likely Typos/MissingSpaceTypo.qhelp new file mode 100644 index 00000000000..da899e8e4ac --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/MissingSpaceTypo.qhelp @@ -0,0 +1,40 @@ + + + + +

    +Splitting a long string literal over multiple lines can often aid readability, +but this also makes it difficult to notice whether a space is missing where the +strings are concatenated. +

    +
    + + +

    +Check the string literal to see whether it has the intended text. +In particular, look for missing spaces near line breaks. +

    + +
    + + +

    +The following example shows a text literal that is split over two lines and +omits a space character between the two words at the line break. +

    + + +
    + + + +
  • +Java Language Specification: +String Literals. +
  • + +
    + +
    diff --git a/java/ql/src/Likely Bugs/Likely Typos/MissingSpaceTypo.ql b/java/ql/src/Likely Bugs/Likely Typos/MissingSpaceTypo.ql new file mode 100644 index 00000000000..5bbe10f9bf0 --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/MissingSpaceTypo.ql @@ -0,0 +1,29 @@ +/** + * @name Missing space in string literal + * @description Joining strings at compile-time to construct a string literal + * so that two words are concatenated without a separating space + * usually indicates a text error. + * @kind problem + * @problem.severity recommendation + * @precision very-high + * @id java/missing-space-in-concatenation + * @tags readability + */ +import java + +class SourceStringLiteral extends StringLiteral { + SourceStringLiteral() { + this.getCompilationUnit().fromSource() + } +} + +from SourceStringLiteral s, string word +where + // Match ` word" + "word2` taking punctuation after `word` into account. + // `word2` is only matched on the first character whereas `word` is matched + // completely to distinguish grammatical punctuation after which a space is + // needed, and intra-identifier punctuation in, for example, a fully + // qualified java class name. + s.getLiteral().regexpCapture(".* (([-A-Za-z/'\\.:,]*[a-zA-Z]|[0-9]+)[\\.:,;!?']*)\"[^\"]*\\+[^\"]*\"[a-zA-Z].*", 1) = word and + not word.regexpMatch(".*[,\\.:].*[a-zA-Z].*[^a-zA-Z]") +select s, "This string appears to be missing a space after '" + word + "'." diff --git a/java/ql/src/Likely Bugs/Likely Typos/MissingSpaceTypoBad.java b/java/ql/src/Likely Bugs/Likely Typos/MissingSpaceTypoBad.java new file mode 100644 index 00000000000..4cd24d5758c --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/MissingSpaceTypoBad.java @@ -0,0 +1,2 @@ +String s = "This text is" + + "missing a space."; diff --git a/java/ql/src/Likely Bugs/Likely Typos/NestedLoopsSameVariable.qhelp b/java/ql/src/Likely Bugs/Likely Typos/NestedLoopsSameVariable.qhelp new file mode 100644 index 00000000000..485e8d7973a --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/NestedLoopsSameVariable.qhelp @@ -0,0 +1,30 @@ + + + + + +

    The behavior of nested loops in which the iteration variable is the same for both loops +is difficult to understand because the inner loop affects the iteration variable of the outer loop. +This is probably a typographical error. +

    + +
    + + +

    Ensure that a different iteration variable is used for each loop. +

    + +
    + + + +
  • + The Java Language Specification: + The basic for Statement. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Likely Typos/NestedLoopsSameVariable.ql b/java/ql/src/Likely Bugs/Likely Typos/NestedLoopsSameVariable.ql new file mode 100644 index 00000000000..bdf710d506f --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/NestedLoopsSameVariable.ql @@ -0,0 +1,24 @@ +/** + * @name Nested loops with same variable + * @description Nested loops in which the iteration variable is the same for each loop are difficult + * to understand. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/nested-loops-with-same-variable + * @tags maintainability + * correctness + * logic + */ +import java + +from ForStmt inner, Variable iteration, ForStmt outer +where + iteration = inner.getAnIterationVariable() and + iteration = outer.getAnIterationVariable() and + inner.getParent+() = outer and + inner.getBasicBlock().getABBSuccessor+() = outer.getCondition().getBasicBlock() +select + inner.getCondition(), "Nested for statement uses loop variable $@ of enclosing $@.", + iteration, iteration.getName(), + outer, "for statement" diff --git a/java/ql/src/Likely Bugs/Likely Typos/SelfAssignment.java b/java/ql/src/Likely Bugs/Likely Typos/SelfAssignment.java new file mode 100644 index 00000000000..07435207f1a --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/SelfAssignment.java @@ -0,0 +1,13 @@ +static public MotionEvent obtainNoHistory(MotionEvent o) { + MotionEvent ev = obtain(o.mNumPointers, 1); + ev.mDeviceId = o.mDeviceId; + o.mFlags = o.mFlags; // Variable is assigned to itself + ... +} + +static public MotionEvent obtainNoHistory(MotionEvent o) { + MotionEvent ev = obtain(o.mNumPointers, 1); + ev.mDeviceId = o.mDeviceId; + ev.mFlags = o.mFlags; // Variable is assigned correctly + ... +} diff --git a/java/ql/src/Likely Bugs/Likely Typos/SelfAssignment.qhelp b/java/ql/src/Likely Bugs/Likely Typos/SelfAssignment.qhelp new file mode 100644 index 00000000000..43444bb32ec --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/SelfAssignment.qhelp @@ -0,0 +1,41 @@ + + + + + +

    Assigning a variable to itself does not have any effect. Therefore, such an assignment is either +completely unnecessary, or it indicates a typo or a similar mistake.

    + +
    + + +

    If the assignment is unnecessary, remove it. If the assignment indicates a typo or a similar mistake, correct +the mistake.

    + +
    + + +

    The following example shows part of a method that is intended to make a copy of an existing +MotionEvent without preserving its history. On line 8, o.mFlags is +assigned to itself. Given that the statement is surrounded by +statements that transfer information from the fields of o to the +fields of the new event, ev, the statement is clearly a mistake. To correct this, the +mFlags value should be assigned to ev.mFlags instead, as shown in the +corrected method.

    + + + +
    + + + +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Likely Typos/SelfAssignment.ql b/java/ql/src/Likely Bugs/Likely Typos/SelfAssignment.ql new file mode 100644 index 00000000000..29618cfcb2e --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/SelfAssignment.ql @@ -0,0 +1,49 @@ +/** + * @name Self assignment + * @description Assigning a variable to itself has no effect. + * @kind problem + * @problem.severity error + * @precision very-high + * @id java/redundant-assignment + * @tags reliability + * correctness + * logic + */ + +import java + +predicate toCompare(VarAccess left, VarAccess right) { + exists(AssignExpr assign | assign.getDest() = left and assign.getSource() = right) + or + exists(VarAccess outerleft, VarAccess outerright | + toCompare(outerleft, outerright) and + left = outerleft.getQualifier() and + right = outerright.getQualifier() + ) +} + +predicate local(RefType enclosingType, VarAccess v) { + enclosingType = v.getQualifier().(ThisAccess).getType() or + not exists(v.getQualifier()) and enclosingType = v.getEnclosingCallable().getDeclaringType() +} + +predicate sameVariable(VarAccess left, VarAccess right) { + toCompare(left, right) and + left.getVariable() = right.getVariable() and + ( + exists(Expr q1, Expr q2 | + q1 = left.getQualifier() and + sameVariable(q1, q2) and + q2 = right.getQualifier() + ) or + exists(RefType enclosingType | + local(enclosingType, left) and local(enclosingType, right) + ) + ) +} + +from AssignExpr assign +where sameVariable(assign.getDest(), assign.getSource()) +select assign, + "This assigns the variable " + assign.getDest().(VarAccess).getVariable().getName() + + " to itself and has no effect." diff --git a/java/ql/src/Likely Bugs/Likely Typos/StringBufferCharInit.java b/java/ql/src/Likely Bugs/Likely Typos/StringBufferCharInit.java new file mode 100644 index 00000000000..d4d3ee132eb --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/StringBufferCharInit.java @@ -0,0 +1,18 @@ +class Point { + private double x, y; + + public Point(double x, double y) { + this.x = x; + this.y = y; + } + + @Override + public String toString() { + StringBuffer res = new StringBuffer('('); + res.append(x); + res.append(", "); + res.append(y); + res.append(')'); + return res.toString(); + } +} diff --git a/java/ql/src/Likely Bugs/Likely Typos/StringBufferCharInit.qhelp b/java/ql/src/Likely Bugs/Likely Typos/StringBufferCharInit.qhelp new file mode 100644 index 00000000000..ad7bdea703f --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/StringBufferCharInit.qhelp @@ -0,0 +1,57 @@ + + + +

    Passing a character to the constructor of StringBuffer or StringBuilder +is probably intended to insert the character into the newly created buffer. In fact, however, the +character value is converted to an integer and interpreted as the buffer's initial capacity, which +may yield unexpected results.

    + +
    + + +

    The following example shows a class representing points in two-dimensional Cartesian coordinates. +The toString method uses a StringBuffer to construct a human-readable +representation of the form (x, y), where x and y are the +point's coordinates.

    + +

    However, the opening parenthesis is passed to the StringBuffer constructor as character +literal. Instead of being used to initialise the buffer's contents, the character is converted to the +integer value 40 and interpreted as the buffer's initial capacity. Thus, the string representation +returned by toString will be missing the opening parenthesis. (Note that passing a character +to append, on the other hand, is unproblematic.)

    + + + +
    + + +

    If the character used to initialize the buffer is a character literal, simply replace it with the +corresponding string literal. So, in our example, replace new StringBuffer('(') with +new StringBuffer("("). If the character is not a literal value, use method +String.valueOf to convert it to a string.

    + +
    + + + +
  • +J. Bloch and N. Gafter, Java Puzzlers: Traps, Pitfalls, and Corner Cases, Puzzle 23. +Addison-Wesley, 2005. +
  • +
  • +NetBeans IDE: Java Hints +
  • +
  • +PMD: Rule StringBufferInstantiationWithChar +
  • +
  • +Java API: +java.lang.StringBuffer, +java.lang.StringBuilder. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Likely Typos/StringBufferCharInit.ql b/java/ql/src/Likely Bugs/Likely Typos/StringBufferCharInit.ql new file mode 100644 index 00000000000..15fb7b076f9 --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/StringBufferCharInit.ql @@ -0,0 +1,30 @@ +/** + * @name Character passed to StringBuffer or StringBuilder constructor + * @description A character value is passed to the constructor of 'StringBuffer' or 'StringBuilder'. This value will + * be converted to an integer and interpreted as the buffer's initial capacity, which is probably not intended. + * @kind problem + * @problem.severity error + * @precision very-high + * @id java/string-buffer-char-init + * @tags reliability + * maintainability + */ +import java + +class NewStringBufferOrBuilder extends ClassInstanceExpr { + NewStringBufferOrBuilder() { + exists(Class c | c = this.getConstructedType() | + c.hasQualifiedName("java.lang", "StringBuilder") or + c.hasQualifiedName("java.lang", "StringBuffer") + ) + } + + string getName() { + result = this.getConstructedType().getName() + } +} + +from NewStringBufferOrBuilder nsb +where nsb.getArgument(0).getType().hasName("char") +select nsb, "A character value passed to 'new " + nsb.getName() + + "' is interpreted as the buffer capacity." diff --git a/java/ql/src/Likely Bugs/Likely Typos/ToStringTypo.java b/java/ql/src/Likely Bugs/Likely Typos/ToStringTypo.java new file mode 100644 index 00000000000..027201f835e --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/ToStringTypo.java @@ -0,0 +1,12 @@ +public class Customer +{ + private String title; + private String forename; + private String surname; + + // ... + + public String tostring() { // The method is named 'tostring'. + return title + " " + forename + " " + surname; + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Likely Typos/ToStringTypo.qhelp b/java/ql/src/Likely Bugs/Likely Typos/ToStringTypo.qhelp new file mode 100644 index 00000000000..eb879232dfb --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/ToStringTypo.qhelp @@ -0,0 +1,36 @@ + + + + + +

    A method named tostring may be a typographical error. +toString (different capitalization) may have been intended instead.

    + +
    + + +

    Ensure that any such method is intended to have this name. Even if it is, it may be better to +rename it to avoid confusion with the inherited method Object.toString.

    + +
    + + +

    The following example shows a method named tostring. It may be better to rename it.

    + + + +
    + + + +
  • +Java 2 Platform, Standard Edition 5.0, API Specification: + +toString. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Likely Typos/ToStringTypo.ql b/java/ql/src/Likely Bugs/Likely Typos/ToStringTypo.ql new file mode 100644 index 00000000000..b310bda898a --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/ToStringTypo.ql @@ -0,0 +1,18 @@ +/** + * @name Typo in toString + * @description A method named 'tostring' may be intended to be named 'toString'. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/tostring-typo + * @tags maintainability + * readability + * naming + */ +import java + +from Method m +where + m.hasName("tostring") and + m.hasNoParameters() +select m, "Should this method be called 'toString' rather than 'tostring'?" diff --git a/java/ql/src/Likely Bugs/Likely Typos/UnusedFormatArg.qhelp b/java/ql/src/Likely Bugs/Likely Typos/UnusedFormatArg.qhelp new file mode 100644 index 00000000000..944e9d86a48 --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/UnusedFormatArg.qhelp @@ -0,0 +1,26 @@ + + + + + + +

    + Change the format string to use all the arguments, or remove the unnecessary arguments. +

    +
    + + +

    +The following example supplies three arguments to be formatted, but the +format string only refers to two arguments, so this will silently ignore the +third argument. +

    + + +
    + + + +
    diff --git a/java/ql/src/Likely Bugs/Likely Typos/UnusedFormatArg.ql b/java/ql/src/Likely Bugs/Likely Typos/UnusedFormatArg.ql new file mode 100644 index 00000000000..01cd76212d2 --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/UnusedFormatArg.ql @@ -0,0 +1,33 @@ +/** + * @name Unused format argument + * @description A format call with a format string that refers to fewer + * arguments than the number of supplied arguments will silently + * ignore the additional arguments. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id java/unused-format-argument + * @tags maintainability + * useless-code + * external/cwe/cwe-685 + */ +import java +import semmle.code.java.StringFormat + +int getNumberOfReferencedIndices(FormattingCall fmtcall) { + exists(int maxref, int skippedrefs | + maxref = max(FormatString fmt | fmtcall.getAFormatString() = fmt | fmt.getMaxFmtSpecIndex()) and + skippedrefs = count(int i | + forex(FormatString fmt | fmtcall.getAFormatString() = fmt | i = fmt.getASkippedFmtSpecIndex()) + ) and + result = maxref - skippedrefs + ) +} + +from FormattingCall fmtcall, int refs, int args +where + refs = getNumberOfReferencedIndices(fmtcall) and + args = fmtcall.getVarargsCount() and + refs < args and + not (fmtcall.hasTrailingThrowableArgument() and refs = args - 1) +select fmtcall, "This format call refers to " + refs + " argument(s) but supplies " + args + " argument(s)." diff --git a/java/ql/src/Likely Bugs/Likely Typos/UnusedFormatArgBad.java b/java/ql/src/Likely Bugs/Likely Typos/UnusedFormatArgBad.java new file mode 100644 index 00000000000..e7aa8ca86be --- /dev/null +++ b/java/ql/src/Likely Bugs/Likely Typos/UnusedFormatArgBad.java @@ -0,0 +1 @@ +System.out.format("First string: %s Second string: %s", "Hello", "world", "!"); diff --git a/java/ql/src/Likely Bugs/Nullness/NullAlways.java b/java/ql/src/Likely Bugs/Nullness/NullAlways.java new file mode 100644 index 00000000000..7db6c26869f --- /dev/null +++ b/java/ql/src/Likely Bugs/Nullness/NullAlways.java @@ -0,0 +1,9 @@ +public void createDir(File dir) { + if (dir != null || !dir.exists()) // BAD + dir.mkdir(); +} + +public void createDir(File dir) { + if (dir != null && !dir.exists()) // GOOD + dir.mkdir(); +} diff --git a/java/ql/src/Likely Bugs/Nullness/NullAlways.qhelp b/java/ql/src/Likely Bugs/Nullness/NullAlways.qhelp new file mode 100644 index 00000000000..c5cee6b4e89 --- /dev/null +++ b/java/ql/src/Likely Bugs/Nullness/NullAlways.qhelp @@ -0,0 +1,48 @@ + + + + + +

    If a variable is dereferenced, and the variable has a null +value on all possible execution paths leading to the dereferencing, the dereferencing is +guaranteed to result in a NullPointerException. +

    +

    +A variable may also be implicitly dereferenced if its type is a boxed primitive +type, and the variable occurs in a context in which implicit unboxing occurs. +Note that the conditional operator unboxes its second and third operands when +one of them is a primitive type and the other is the corresponding boxed type. +

    + +
    + + +

    Ensure that the variable does not have a null value when it is dereferenced. +

    + +
    + +

    +In the following examples, the condition !dir.exists() is only +executed if dir is null. The second example guards +the expression correctly by using && instead of +||. +

    + + +
    + + +
  • +Java Tutorial: +Autoboxing and Unboxing. +
  • +
  • +Java Specification: +Conditional Operator ? :. +
  • + +
    +
    diff --git a/java/ql/src/Likely Bugs/Nullness/NullAlways.ql b/java/ql/src/Likely Bugs/Nullness/NullAlways.ql new file mode 100644 index 00000000000..6e07f875022 --- /dev/null +++ b/java/ql/src/Likely Bugs/Nullness/NullAlways.ql @@ -0,0 +1,20 @@ +/** + * @name Dereferenced variable is always null + * @description Dereferencing a variable whose value is 'null' causes a 'NullPointerException'. + * @kind problem + * @problem.severity error + * @precision very-high + * @id java/dereferenced-value-is-always-null + * @tags reliability + * correctness + * exceptions + * external/cwe/cwe-476 + */ + +import java +private import semmle.code.java.dataflow.SSA +private import semmle.code.java.dataflow.Nullness + +from VarAccess access, SsaSourceVariable var +where alwaysNullDeref(var, access) +select access, "Variable $@ is always null here.", var.getVariable(), var.getVariable().getName() diff --git a/java/ql/src/Likely Bugs/Nullness/NullExprDeref.java b/java/ql/src/Likely Bugs/Nullness/NullExprDeref.java new file mode 100644 index 00000000000..fa843c7f335 --- /dev/null +++ b/java/ql/src/Likely Bugs/Nullness/NullExprDeref.java @@ -0,0 +1,3 @@ +public int getID() { + return helper == null ? null : helper.getID(); +} diff --git a/java/ql/src/Likely Bugs/Nullness/NullExprDeref.qhelp b/java/ql/src/Likely Bugs/Nullness/NullExprDeref.qhelp new file mode 100644 index 00000000000..6ec57262ebf --- /dev/null +++ b/java/ql/src/Likely Bugs/Nullness/NullExprDeref.qhelp @@ -0,0 +1,39 @@ + + + + +

    Dereferencing a null value leads to a NullPointerException. +

    +

    +An expression may be implicitly dereferenced if its type is a boxed primitive +type, and it occurs in a context in which implicit unboxing occurs. +

    +
    + + +

    Ensure that the expression does not have a null value when it is dereferenced. +Use boxed types as appropriate to hold values that are potentially null. +

    +
    + + +

    +In the following example implicit unboxing can cause a NullPointerException if helper is null. +

    + +

    +If the method is intended to return null, the return type should be changed to Integer. +

    +
    + + + +
  • +Java Tutorial: +Autoboxing and Unboxing. +
  • + +
    +
    diff --git a/java/ql/src/Likely Bugs/Nullness/NullExprDeref.ql b/java/ql/src/Likely Bugs/Nullness/NullExprDeref.ql new file mode 100644 index 00000000000..7929c839a41 --- /dev/null +++ b/java/ql/src/Likely Bugs/Nullness/NullExprDeref.ql @@ -0,0 +1,20 @@ +/** + * @name Dereferenced expression may be null + * @description Dereferencing an expression whose value may be 'null' may cause a + * 'NullPointerException'. + * @kind problem + * @problem.severity warning + * @precision high + * @id java/dereferenced-expr-may-be-null + * @tags reliability + * correctness + * exceptions + * external/cwe/cwe-476 + */ + +import java +import semmle.code.java.dataflow.Nullness + +from Expr e +where dereference(e) and e = nullExpr() +select e, "This expression is dereferenced and may be null." diff --git a/java/ql/src/Likely Bugs/Nullness/NullMaybe.java b/java/ql/src/Likely Bugs/Nullness/NullMaybe.java new file mode 100644 index 00000000000..40cbad56934 --- /dev/null +++ b/java/ql/src/Likely Bugs/Nullness/NullMaybe.java @@ -0,0 +1,3 @@ +public Integer f(Integer p) { + return true ? p : 5; +} diff --git a/java/ql/src/Likely Bugs/Nullness/NullMaybe.qhelp b/java/ql/src/Likely Bugs/Nullness/NullMaybe.qhelp new file mode 100644 index 00000000000..f03e62ecb5b --- /dev/null +++ b/java/ql/src/Likely Bugs/Nullness/NullMaybe.qhelp @@ -0,0 +1,53 @@ + + + + + +

    If a variable is dereferenced, and the variable may have a null +value on some execution paths leading to the dereferencing, the dereferencing +may result in a NullPointerException. +

    +

    +A variable may also be implicitly dereferenced if its type is a boxed primitive +type, and the variable occurs in a context in which implicit unboxing occurs. +Note that the conditional operator unboxes its second and third operands when +one of them is a primitive type and the other is the corresponding boxed type. +

    + +
    + + +

    Ensure that the variable does not have a null value when it is dereferenced. +

    + +
    + +

    +In the following example, the use of the conditional operator causes implicit +unboxing, since the integer literal has type int. +If the parameter p is ever +null then a NullPointerException will occur. +

    + + + +

    +If the implicit unboxing is unintentional, it can be prevented by making sure +that both branches of the conditional operator have the same type. +

    +
    + + +
  • +Java Tutorial: +Autoboxing and Unboxing. +
  • +
  • +Java Specification: +Conditional Operator ? :. +
  • + +
    +
    diff --git a/java/ql/src/Likely Bugs/Nullness/NullMaybe.ql b/java/ql/src/Likely Bugs/Nullness/NullMaybe.ql new file mode 100644 index 00000000000..cadcf6d04ab --- /dev/null +++ b/java/ql/src/Likely Bugs/Nullness/NullMaybe.ql @@ -0,0 +1,26 @@ +/** + * @name Dereferenced variable may be null + * @description Dereferencing a variable whose value may be 'null' may cause a + * 'NullPointerException'. + * @kind problem + * @problem.severity warning + * @precision high + * @id java/dereferenced-value-may-be-null + * @tags reliability + * correctness + * exceptions + * external/cwe/cwe-476 + * non-local + */ + +import java +private import semmle.code.java.dataflow.SSA +private import semmle.code.java.dataflow.Nullness + +from VarAccess access, SsaSourceVariable var, string msg, Expr reason +where + nullDeref(var, access, msg, reason) and + // Exclude definite nulls here, as these are covered by `NullAlways.ql`. + not alwaysNullDeref(var, access) +select access, "Variable $@ may be null here " + msg + ".", + var.getVariable(), var.getVariable().getName(), reason, "this" diff --git a/java/ql/src/Likely Bugs/Reflection/AnnotationPresentCheck.java b/java/ql/src/Likely Bugs/Reflection/AnnotationPresentCheck.java new file mode 100644 index 00000000000..c6de814fec1 --- /dev/null +++ b/java/ql/src/Likely Bugs/Reflection/AnnotationPresentCheck.java @@ -0,0 +1,17 @@ +public class AnnotationPresentCheck { + public static @interface UntrustedData { } + + @UntrustedData + public static String getUserData() { + Scanner scanner = new Scanner(System.in); + return scanner.nextLine(); + } + + public static void main(String[] args) throws NoSuchMethodException, SecurityException { + String data = getUserData(); + Method m = AnnotationPresentCheck.class.getMethod("getUserData"); + if(m.isAnnotationPresent(UntrustedData.class)) { // Returns 'false' + System.out.println("Not trusting data from user."); + } + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Reflection/AnnotationPresentCheck.qhelp b/java/ql/src/Likely Bugs/Reflection/AnnotationPresentCheck.qhelp new file mode 100644 index 00000000000..4c67123614f --- /dev/null +++ b/java/ql/src/Likely Bugs/Reflection/AnnotationPresentCheck.qhelp @@ -0,0 +1,46 @@ + + + + + +

    To be able to use the isAnnotationPresent method on an AnnotatedElement at runtime, +an annotation must be explicitly annotated with a RUNTIME retention policy. +Otherwise, the annotation is not retained at runtime and cannot be observed using reflection. +

    + +
    + + +

    Explicitly annotate annotations with a RUNTIME retention policy +if you want to observe their presence using AnnotatedElement.isAnnotationPresent +at runtime. +

    + +
    + + +

    In the following example, the call to isAnnotationPresent returns false +because the annotation cannot be observed using reflection.

    + + + +

    To correct this, the annotation is annotated with a RUNTIME retention policy.

    + + + +
    + + + +
  • + Java API Documentation: + Annotation Type Retention, + RetentionPolicy.RUNTIME, + AnnotatedElement.isAnnotationPresent(). +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Reflection/AnnotationPresentCheck.ql b/java/ql/src/Likely Bugs/Reflection/AnnotationPresentCheck.ql new file mode 100644 index 00000000000..6c77168c213 --- /dev/null +++ b/java/ql/src/Likely Bugs/Reflection/AnnotationPresentCheck.ql @@ -0,0 +1,26 @@ +/** + * @name AnnotationPresent check + * @description If an annotation has not been annotated with a 'RUNTIME' retention policy, checking + * for its presence at runtime is not possible. + * @kind problem + * @problem.severity error + * @precision medium + * @id java/ineffective-annotation-present-check + * @tags correctness + * logic + */ +import java + +from MethodAccess c, Method m, ParameterizedClass p, AnnotationType t +where + c.getMethod() = m and + m.hasName("isAnnotationPresent") and + m.getNumberOfParameters() = 1 and + c.getArgument(0).getType() = p and + p.getATypeArgument() = t and + not exists(Annotation a | + t.getAnAnnotation() = a and + a.getType().hasQualifiedName("java.lang.annotation", "Retention") and + a.getAValue().(VarAccess).getVariable().hasName("RUNTIME") + ) +select c, "Call to isAnnotationPresent where no annotation has the RUNTIME retention policy." diff --git a/java/ql/src/Likely Bugs/Reflection/AnnotationPresentCheckFix.java b/java/ql/src/Likely Bugs/Reflection/AnnotationPresentCheckFix.java new file mode 100644 index 00000000000..726bd0eff89 --- /dev/null +++ b/java/ql/src/Likely Bugs/Reflection/AnnotationPresentCheckFix.java @@ -0,0 +1,18 @@ +public class AnnotationPresentCheckFix { + @Retention(RetentionPolicy.RUNTIME) // Annotate the annotation + public static @interface UntrustedData { } + + @UntrustedData + public static String getUserData() { + Scanner scanner = new Scanner(System.in); + return scanner.nextLine(); + } + + public static void main(String[] args) throws NoSuchMethodException, SecurityException { + String data = getUserData(); + Method m = AnnotationPresentCheckFix.class.getMethod("getUserData"); + if(m.isAnnotationPresent(UntrustedData.class)) { // Returns 'true' + System.out.println("Not trusting data from user."); + } + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.java b/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.java new file mode 100644 index 00000000000..3b1fa94cd2f --- /dev/null +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.java @@ -0,0 +1,7 @@ +public class CloseReader { + public static void main(String[] args) throws IOException { + BufferedReader br = new BufferedReader(new FileReader("C:\\test.txt")); + System.out.println(br.readLine()); + // ... + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.qhelp b/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.qhelp new file mode 100644 index 00000000000..3de3880c0f1 --- /dev/null +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.qhelp @@ -0,0 +1,62 @@ + + + + + +

    A subclass of Reader or InputStream that is opened for reading +but not closed may cause a resource leak. +

    + +
    + + +

    Ensure that the resource is always closed to avoid a resource leak. Note that, because of exceptions, +it is safest to close a resource in a finally block. (However, this is unnecessary for +subclasses of StringReader and ByteArrayInputStream.) +

    + +

    For Java 7 or later, the recommended way to close resources that implement java.lang.AutoCloseable +is to declare them within a try-with-resources statement, so that they are closed implicitly.

    + +
    + + +

    In the following example, the resource br is opened but not closed.

    + + + +

    In the following example, the resource br is opened in a try block and +later closed in a finally block.

    + + + +

    Note that nested class instance creation expressions of Readers or InputStreams +are not safe to use if the constructor of the outer expression may throw an exception. In the following +example, the InputStreamReader may throw an exception, in which case the inner FileInputStream +is not closed. +

    + + + +

    +In this case, the inner expression needs to be assigned to a local variable and closed separately, as shown below. +

    + + + +
    + + + +
  • + IBM developerWorks: Java theory and practice: Good housekeeping practices. +
  • +
  • + The Java Tutorials: The try-with-resources Statement. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.ql b/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.ql new file mode 100644 index 00000000000..5da821a7626 --- /dev/null +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.ql @@ -0,0 +1,40 @@ +/** + * @name Potential input resource leak + * @description A resource that is opened for reading but not closed may cause a resource + * leak. + * @kind problem + * @problem.severity error + * @precision high + * @id java/input-resource-leak + * @tags efficiency + * correctness + * resources + * external/cwe/cwe-404 + * external/cwe/cwe-772 + */ +import CloseType + +predicate readerType(RefType t) { + exists(RefType sup | sup = t.getASupertype*() | + sup.hasName("Reader") or + sup.hasName("InputStream") or + sup.hasQualifiedName("java.util.zip", "ZipFile") + ) +} + +predicate safeReaderType(RefType t) { + exists(RefType sup | sup = t.getASupertype*() | + sup.hasName("StringReader") or + sup.hasName("ByteArrayInputStream") or + sup.hasName("StringInputStream") + ) +} + +from ClassInstanceExpr cie, RefType t +where + badCloseableInit(cie) and + cie.getType() = t and + readerType(t) and + not safeReaderType(typeInDerivation(cie)) and + not noNeedToClose(cie) +select cie, "This " + t.getName() + " is not always closed on method exit." diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseReaderGood.java b/java/ql/src/Likely Bugs/Resource Leaks/CloseReaderGood.java new file mode 100644 index 00000000000..3ae696e6568 --- /dev/null +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseReaderGood.java @@ -0,0 +1,14 @@ +public class CloseReaderFix { + public static void main(String[] args) throws IOException { + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader("C:\\test.txt")); + System.out.println(br.readLine()); + } + finally { + if(br != null) + br.close(); // 'br' is closed + } + // ... + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseReaderNested.java b/java/ql/src/Likely Bugs/Resource Leaks/CloseReaderNested.java new file mode 100644 index 00000000000..598efd9be54 --- /dev/null +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseReaderNested.java @@ -0,0 +1,16 @@ +public class CloseReaderNested { + public static void main(String[] args) throws IOException { + InputStreamReader reader = null; + try { + // InputStreamReader may throw an exception, in which case the ... + reader = new InputStreamReader( + // ... FileInputStream is not closed by the finally block + new FileInputStream("C:\\test.txt"), "UTF-8"); + System.out.println(reader.read()); + } + finally { + if (reader != null) + reader.close(); + } + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseReaderNestedGood.java b/java/ql/src/Likely Bugs/Resource Leaks/CloseReaderNestedGood.java new file mode 100644 index 00000000000..daf34de2eaf --- /dev/null +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseReaderNestedGood.java @@ -0,0 +1,17 @@ +public class CloseReaderNestedFix { + public static void main(String[] args) throws IOException { + FileInputStream fis = null; + InputStreamReader reader = null; + try { + fis = new FileInputStream("C:\\test.txt"); + reader = new InputStreamReader(fis); + System.out.println(reader.read()); + } + finally { + if (reader != null) + reader.close(); // 'reader' is closed + if (fis != null) + fis.close(); // 'fis' is closed + } + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseSql.java b/java/ql/src/Likely Bugs/Resource Leaks/CloseSql.java new file mode 100644 index 00000000000..83edb56b48a --- /dev/null +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseSql.java @@ -0,0 +1,9 @@ +public class CloseSql { + public static void runQuery(Connection con, String query) throws SQLException { + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery(query); + while (rs.next()) { + // process result set + } + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseSql.qhelp b/java/ql/src/Likely Bugs/Resource Leaks/CloseSql.qhelp new file mode 100644 index 00000000000..10c72e58efa --- /dev/null +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseSql.qhelp @@ -0,0 +1,51 @@ + + + + + +

    A database resource in the java.sql package that is opened +but not closed may cause a resource leak and ultimately resource exhaustion. +

    + +
    + + +

    Ensure that the resource is always closed to avoid a resource leak. Note that, because of exceptions, +it is safest to close a resource in a finally block. +

    + +

    For Java 7 or later, the recommended way to close resources that implement java.lang.AutoCloseable +is to declare them within a try-with-resources statement, so that they are closed implicitly.

    + +
    + + +

    In the following example, the resources stmt and rs are opened but not closed.

    + + + +

    In the following example, the resources stmt and rs are +declared within a try-with-resources block and are thus closed implicitly.

    + + + +

    Note that the Connection that is passed into the method is a long-lived object +that was created elsewhere and therefore need not be closed locally. It should instead be closed +by the code that created it or by a server shutdown procedure, as appropriate.

    + +
    + + + +
  • + IBM developerWorks: Java theory and practice: Good housekeeping practices. +
  • +
  • + The Java Tutorials: The try-with-resources Statement. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseSql.ql b/java/ql/src/Likely Bugs/Resource Leaks/CloseSql.ql new file mode 100644 index 00000000000..d2681cbb6d1 --- /dev/null +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseSql.ql @@ -0,0 +1,21 @@ +/** + * @name Potential database resource leak + * @description A database resource that is opened but not closed may cause a resource leak. + * @kind problem + * @problem.severity error + * @precision high + * @id java/database-resource-leak + * @tags correctness + * resources + * external/cwe/cwe-404 + * external/cwe/cwe-772 + */ +import CloseType + +from CloseableInitExpr cie, RefType t +where + badCloseableInit(cie) and + cie.getType() = t and + sqlType(t) and + not noNeedToClose(cie) +select cie, "This " + t.getName() + " is not always closed on method exit." diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseSqlGood.java b/java/ql/src/Likely Bugs/Resource Leaks/CloseSqlGood.java new file mode 100644 index 00000000000..c57caeba344 --- /dev/null +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseSqlGood.java @@ -0,0 +1,10 @@ +public class CloseSqlGood { + public static void runQuery(Connection con, String query) throws SQLException { + try (Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery(query)) { + while (rs.next()) { + // process result set + } + } + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseType.qll b/java/ql/src/Likely Bugs/Resource Leaks/CloseType.qll new file mode 100644 index 00000000000..1981c131eab --- /dev/null +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseType.qll @@ -0,0 +1,321 @@ +import java +import semmle.code.java.frameworks.Jdbc +import semmle.code.java.frameworks.Servlets +import semmle.code.java.frameworks.Mockito + +/** + * Expression `e` is assigned to variable `v`. + */ +private +predicate flowsInto(Expr e, Variable v) { + e = v.getAnAssignedValue() or + exists(ParExpr p | flowsInto(p, v) | e = p.getExpr()) or + exists(CastExpr c | flowsInto(c, v) | e = c.getExpr()) or + exists(ConditionalExpr c | flowsInto(c, v) | e = c.getTrueExpr() or e = c.getFalseExpr()) +} + +/** + * A type in the `java.sql` package with a `close` method. + * (Prior to Java 7, these types were not subtypes of `Closeable` or `AutoCloseable`.) + */ +predicate sqlType(RefType t) { + exists(RefType sup | sup = t.getASupertype*() and sup.getAMethod().hasName("close") | + sup.hasQualifiedName("java.sql", "Connection") or + sup.hasQualifiedName("java.sql", "Statement") or + sup.hasQualifiedName("java.sql", "ResultSet") + ) +} + +/** + * A (reflexive, transitive) subtype of `Closeable` or `AutoCloseable`, + * or a closeable type in the `java.sql` package. + */ +private +predicate closeableType(RefType t) { + exists(RefType supertype | supertype = t.getASupertype*() | + supertype.hasName("Closeable") or + supertype.hasName("AutoCloseable") or + sqlType(supertype) + ) +} + +/** + * An access to a method on a type in the 'java.sql` package that creates a closeable object in the `java.sql` package. + * For example, `PreparedStatement.executeQuery()` or `Connection.prepareStatement(String)`. + */ +class SqlResourceOpeningMethodAccess extends MethodAccess { + SqlResourceOpeningMethodAccess() { + exists(Method m | this.getMethod() = m | + m.getDeclaringType().(RefType).hasQualifiedName("java.sql", _) and + m.getReturnType().(RefType).hasQualifiedName("java.sql", _) and + m.getName().regexpMatch("(create|prepare|execute).*") and + closeableType(m.getReturnType()) and + not this.getQualifier() instanceof MockitoMockedObject + ) + } +} + +/** + * A candidate for a "closeable init" expression, which may require calling a "close" method. + */ +class CloseableInitExpr extends Expr { + CloseableInitExpr() { + this instanceof ClassInstanceExpr or + this instanceof SqlResourceOpeningMethodAccess + } +} + +/** + * The expression `e` is either + * - a (possibly nested) class instance creation expression of a closeable type, + * - a SQL method access that returns an object of a closeable type, or + *`- an access to a local variable to which a "closeable init" is assigned. + * + * The expression `parent` is the "closeable init" from which `e` is derived, if any, or `e` itself. + */ +private +predicate closeableInit(Expr e, Expr parent) { + exists(ClassInstanceExpr cie | cie = e | + closeableType(cie.getType()) and + ( + exists(Expr arg | arg = cie.getAnArgument() | + closeableType(arg.getType()) and + parent = arg) + or + ( + not exists(Expr arg | arg = cie.getAnArgument() | closeableType(arg.getType())) and + parent = cie + ) + ) + ) + or + exists(SqlResourceOpeningMethodAccess ma | ma = e and parent = e) + or + exists(LocalVariableDecl v, Expr f | + e = v.getAnAccess() and flowsInto(f, v) + | + closeableInit(f, parent) + ) +} + +/** + * The transitive closure of `closeableInit`. + */ +private +predicate transitiveCloseableInit(Expr init, Expr transParent) { + closeableInit+(init, transParent) +} + +/** + * The expression `root` is the innermost "closeable init" expression of `cie` (possibly itself). + */ +private +predicate closeableInitRootCause(Expr cie, Expr root) { + transitiveCloseableInit(cie, root) and + not exists(Expr other | + transitiveCloseableInit(root, other) and other != root + ) +} + +/** + * The type of the specified class instance creation expression or + * the type of any of the "closeable init" expressions that it is derived from. + */ +RefType typeInDerivation(ClassInstanceExpr cie) { + result = cie.getType() or + exists(Expr transParent | transitiveCloseableInit(cie, transParent) | + result = transParent.getType() + ) +} + +/** + * A "closeable init" whose root cause is not a field or parameter. + */ +private +predicate locallyInitializedCloseable(Expr cie) { + exists(Expr root | closeableInitRootCause(cie, root) | + not exists(VarAccess va | va = root | + va.getVariable() instanceof Parameter or + va.getVariable() instanceof Field) + ) +} + +/** + * A locally initialized "closeable init" whose constructor does not have a throws clause. + */ +private +predicate safeCloseableInit(ClassInstanceExpr cie) { + locallyInitializedCloseable(cie) and + not exists(cie.getConstructor().getAnException()) +} + +/** + * A locally initialized "closeable init" that is neither assigned to a variable nor passed to a safe outer "closeable init". + */ +private +predicate unassignedCloseableInit(CloseableInitExpr cie) { + locallyInitializedCloseable(cie) and + not flowsInto(cie, _) and + not exists(ClassInstanceExpr outer | safeCloseableInit(outer) | cie = outer.getAnArgument()) +} + +/** + * A locally initialized "closeable init" that flows into a field or return statement. + */ +private +predicate escapingCloseableInit(CloseableInitExpr cie) { + exists(Expr wrappingResource | + locallyInitializedCloseable(cie) and (transitiveCloseableInit(wrappingResource, cie) or wrappingResource = cie) + | + exists(Field f | flowsInto(wrappingResource, f)) or + exists(ConstructorCall call | call.callsThis() or call.callsSuper() | + closeableType(call.getConstructedType()) and + call.getAnArgument() = wrappingResource + ) or + wrappingResource.getEnclosingStmt() instanceof ReturnStmt or + getCloseableVariable(wrappingResource).getAnAccess().getEnclosingStmt() instanceof ReturnStmt + or + exists(Parameter p0 | + escapingMethodParameterClosable(p0) + | + p0.getAnArgument() = wrappingResource or + exists(LocalVariableDecl v | + p0.getAnArgument() = v.getAnAccess() and flowsInto(wrappingResource, v) + ) + ) + ) +} + +/** + * Holds if `p` is a closable that escapes by an assignment to a field. + */ +private +predicate escapingMethodParameterClosable(Parameter p) { + p.getCallable() instanceof Method and + exists(Expr wrappingResource | + closeableType(p.getType()) and (transitiveCloseableInit(wrappingResource, p.getAnAccess()) or wrappingResource = p.getAnAccess()) + | + exists(Field f | flowsInto(wrappingResource, f)) or + exists(Parameter p0 | + escapingMethodParameterClosable(p0) + | + p0.getAnArgument() = wrappingResource or + exists(LocalVariableDecl v | + p0.getAnArgument() = v.getAnAccess() and flowsInto(wrappingResource, v) + ) + ) + ) +} + +/** + * A local variable into which the specified (locally initialized) "closeable init" flows. + */ +private +LocalVariableDecl getCloseableVariable(CloseableInitExpr cie) { + locallyInitializedCloseable(cie) and flowsInto(cie, result) +} + +/** + * A variable on which a "close" method is called, implicitly or explicitly, directly or indirectly. + */ +private +predicate closeCalled(Variable v) { + // `close()` is implicitly called on variables declared or referenced + // in the resources clause of try-with-resource statements. + exists(TryStmt try | try.getAResourceVariable() = v) + or + // Otherwise, there should be an explicit call to a method whose name contains "close". + exists(MethodAccess e | + v = getCloseableVariable(_) or v instanceof Parameter or v instanceof LocalVariableDecl + | + ( + e.getMethod().getName().toLowerCase().matches("%close%") and + exists(VarAccess va | va = v.getAnAccess() | + e.getQualifier() = va or + e.getAnArgument() = va + ) + ) + or + // The "close" call could happen indirectly inside a helper method of unknown name. + exists(int i | exprs(v.getAnAccess(), _, _, e, i) | + exists(Parameter p, int j | params(p, _, j, e.getMethod(), _) | + (closeCalled(p) and i = j) or + // The helper method could be iterating over a varargs parameter. + exists(EnhancedForStmt for | for.getExpr() = p.getAnAccess() | + closeCalled(for.getVariable().getVariable())) and p.isVarargs() and j<=i + ) + ) + ) +} + +/** + * A locally initialized "closeable init" that flows into a variable on which a "close" method is called + * or is immediately closed after creation. + */ +private +predicate closedResource(CloseableInitExpr cie) { + locallyInitializedCloseable(cie) and + ( + exists(LocalVariableDecl v | closeCalled(v) | + exists(Expr wrappingResource | + wrappingResource = cie or transitiveCloseableInit(wrappingResource, cie) + | + flowsInto(wrappingResource, v) + ) + ) or + immediatelyClosed(cie) + ) +} + +private predicate immediatelyClosed(ClassInstanceExpr cie) { + exists(MethodAccess ma | ma.getQualifier() = cie | ma.getMethod().hasName("close")) +} + +/** + * A unassigned or locally-assigned "closeable init" that does not escape and is not closed. + */ +private +predicate badCloseableInitImpl(CloseableInitExpr cie) { + (unassignedCloseableInit(cie) and not immediatelyClosed(cie) and not escapingCloseableInit(cie)) or + (locallyInitializedCloseable(cie) and not closedResource(cie) and not escapingCloseableInit(cie)) +} + +/** + * The innermost "closeable init" of `cie` that does not escape and is never closed. + */ +predicate badCloseableInit(CloseableInitExpr cie) { + badCloseableInitImpl(cie) and + not exists(CloseableInitExpr cie2 | transitiveCloseableInit(cie, cie2) and cie2 != cie and badCloseableInitImpl(cie2)) +} + +/** + * A locally initialized "closeable init" that does not need to be closed. + */ +predicate noNeedToClose(CloseableInitExpr cie) { + locallyInitializedCloseable(cie) and + ( + (cie instanceof ClassInstanceExpr and not exists(cie.(ClassInstanceExpr).getAnArgument())) or + exists(RefType t | t = cie.getType() and t.fromSource() | + exists(Method close | close.getDeclaringType() = t and close.getName() = "close" | + not exists(close.getBody().getAChild()) + ) + ) or + exists(CloseableInitExpr sqlStmt, LocalVariableDecl v | + // If a `java.sql.Statement` is closed, an associated `java.sql.ResultSet` is implicitly closed. + sqlStmt.getType().(RefType).getASupertype*() instanceof TypeStatement and + flowsInto(sqlStmt, v) and + closedResource(sqlStmt) and + cie.getType() instanceof TypeResultSet and + cie.(SqlResourceOpeningMethodAccess).getQualifier() = v.getAnAccess() + ) or + exists(MethodAccess ma | cie.(ClassInstanceExpr).getAnArgument() = ma | + ma.getMethod() instanceof ServletResponseGetOutputStreamMethod or + ma.getMethod() instanceof ServletResponseGetWriterMethod or + ma.getMethod() instanceof ServletRequestGetBodyMethod + ) + ) + or + exists(CloseableInitExpr inner | transitiveCloseableInit(cie, inner) | noNeedToClose(inner)) + or + exists(CloseableInitExpr inner | transitiveCloseableInit(cie, inner) | closeCalled(getCloseableVariable(inner))) +} diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.java b/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.java new file mode 100644 index 00000000000..baf5dba796b --- /dev/null +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.java @@ -0,0 +1,7 @@ +public class CloseWriter { + public static void main(String[] args) throws IOException { + BufferedWriter bw = new BufferedWriter(new FileWriter("C:\\test.txt")); + bw.write("Hello world!"); + // ... + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.qhelp b/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.qhelp new file mode 100644 index 00000000000..1e768567f48 --- /dev/null +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.qhelp @@ -0,0 +1,61 @@ + + + + + +

    A subclass of Writer or OutputStream that is opened for writing +but not properly closed later may cause a resource leak. +

    + +
    + + +

    Ensure that the resource is always closed to avoid a resource leak. Note that, because of exceptions, +it is safest to close a resource properly in a finally block. (However, this is unnecessary for +subclasses of StringWriter and ByteArrayOutputStream.)

    + +

    For Java 7 or later, the recommended way to close resources that implement java.lang.AutoCloseable +is to declare them within a try-with-resources statement, so that they are closed implicitly.

    + +
    + + +

    In the following example, the resource bw is opened but not closed.

    + + + +

    In the following example, the resource bw is opened in a try block and +later closed in a finally block.

    + + + +

    Note that nested class instance creation expressions of Writers or OutputStreams +are not safe to use if the constructor of the outer expression may throw an exception. In the following +example, the OutputStreamWriter may throw an exception, in which case the inner FileOutputStream +is not closed. +

    + + + +

    +In this case, the inner expression needs to be assigned to a local variable and closed separately, as shown below. +

    + + + +
    + + + +
  • + IBM developerWorks: Java theory and practice: Good housekeeping practices. +
  • +
  • + The Java Tutorials: The try-with-resources Statement. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.ql b/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.ql new file mode 100644 index 00000000000..f0ce36cc821 --- /dev/null +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.ql @@ -0,0 +1,38 @@ +/** + * @name Potential output resource leak + * @description A resource that is opened for writing but not closed may cause a resource + * leak. + * @kind problem + * @problem.severity error + * @precision high + * @id java/output-resource-leak + * @tags efficiency + * correctness + * resources + * external/cwe/cwe-404 + * external/cwe/cwe-772 + */ +import CloseType + +predicate writerType(RefType t) { + exists(RefType sup | sup = t.getASupertype*() | + sup.hasName("Writer") or + sup.hasName("OutputStream") + ) +} + +predicate safeWriterType(RefType t) { + exists(RefType sup | sup = t.getASupertype*() | + sup.hasName("StringWriter") or + sup.hasName("ByteArrayOutputStream") + ) +} + +from ClassInstanceExpr cie, RefType t +where + badCloseableInit(cie) and + cie.getType() = t and + writerType(t) and + not safeWriterType(typeInDerivation(cie)) and + not noNeedToClose(cie) +select cie, "This " + t.getName() + " is not always closed on method exit." diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseWriterGood.java b/java/ql/src/Likely Bugs/Resource Leaks/CloseWriterGood.java new file mode 100644 index 00000000000..5e1c9102237 --- /dev/null +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseWriterGood.java @@ -0,0 +1,14 @@ +public class CloseWriterFix { + public static void main(String[] args) throws IOException { + BufferedWriter bw = null; + try { + bw = new BufferedWriter(new FileWriter("C:\\test.txt")); + bw.write("Hello world!"); + } + finally { + if(bw != null) + bw.close(); // 'bw' is closed + } + // ... + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseWriterNested.java b/java/ql/src/Likely Bugs/Resource Leaks/CloseWriterNested.java new file mode 100644 index 00000000000..76600fa18ff --- /dev/null +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseWriterNested.java @@ -0,0 +1,16 @@ +public class CloseWriterNested { + public static void main(String[] args) throws IOException { + OutputStreamWriter writer = null; + try { + // OutputStreamWriter may throw an exception, in which case the ... + writer = new OutputStreamWriter( + // ... FileOutputStream is not closed by the finally block + new FileOutputStream("C:\\test.txt"), "UTF-8"); + writer.write("Hello world!"); + } + finally { + if (writer != null) + writer.close(); + } + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseWriterNestedGood.java b/java/ql/src/Likely Bugs/Resource Leaks/CloseWriterNestedGood.java new file mode 100644 index 00000000000..44d7d55d2d7 --- /dev/null +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseWriterNestedGood.java @@ -0,0 +1,17 @@ +public class CloseWriterNestedFix { + public static void main(String[] args) throws IOException { + FileOutputStream fos = null; + OutputStreamWriter writer = null; + try { + fos = new FileOutputStream("C:\\test.txt"); + writer = new OutputStreamWriter(fos); + writer.write("Hello world!"); + } + finally { + if (writer != null) + writer.close(); // 'writer' is closed + if (fos != null) + fos.close(); // 'fos' is closed + } + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Serialization/IncorrectSerialVersionUID.java b/java/ql/src/Likely Bugs/Serialization/IncorrectSerialVersionUID.java new file mode 100644 index 00000000000..91ee7d116cd --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/IncorrectSerialVersionUID.java @@ -0,0 +1,11 @@ +class WrongNote implements Serializable { + // BAD: serialVersionUID must be static, final, and 'long' + private static final int serialVersionUID = 1; + + //... +} + +class Note implements Serializable { + // GOOD: serialVersionUID is of the correct type + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Serialization/IncorrectSerialVersionUID.qhelp b/java/ql/src/Likely Bugs/Serialization/IncorrectSerialVersionUID.qhelp new file mode 100644 index 00000000000..c80be5f5f18 --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/IncorrectSerialVersionUID.qhelp @@ -0,0 +1,42 @@ + + + + + +

    +A serializable class that uses the serialVersionUID field to act as an object version number must declare the field +to be final, static, and of type long for it to be used by the Java serialization framework. +

    + +
    + +

    +Make sure that the serialVersionUID field in a serialized class is final, static, and of type long. +

    + +
    + + +

    In the following example, WrongNote defines serialVersionUID using the wrong +type, so that it is not used by the Java serialization framework. However, Note defines it correctly +so that it is used by the framework.

    + + + +
    + + + +
  • + Java API Documentation: + Serializable. +
  • +
  • + JavaWorld: Ensure proper version control for serialized objects. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Serialization/IncorrectSerialVersionUID.ql b/java/ql/src/Likely Bugs/Serialization/IncorrectSerialVersionUID.ql new file mode 100644 index 00000000000..65385db5fa8 --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/IncorrectSerialVersionUID.ql @@ -0,0 +1,24 @@ +/** + * @name Incorrect serialVersionUID field + * @description A 'serialVersionUID' field that is declared in a serializable class but is of the + * wrong type cannot be used by the serialization framework. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/incorrect-serial-version-uid + * @tags reliability + * maintainability + * language-features + */ +import java + +from Field f +where + f.hasName("serialVersionUID") and + ( + not f.isFinal() or + not f.isStatic() or + not f.getType().hasName("long") + ) and + exists(TypeSerializable serializable | f.getDeclaringType().getASupertype+() = serializable) +select f, "serialVersionUID should be final, static, and of type long." diff --git a/java/ql/src/Likely Bugs/Serialization/IncorrectSerializableMethods.java b/java/ql/src/Likely Bugs/Serialization/IncorrectSerializableMethods.java new file mode 100644 index 00000000000..33599f15e25 --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/IncorrectSerializableMethods.java @@ -0,0 +1,25 @@ +class WrongNetRequest implements Serializable { + // BAD: Does not match the exact signature required for a custom + // deserialization protocol. Will not be called during deserialization. + void readObject(ObjectInputStream in) { + //... + } + + // BAD: Does not match the exact signature required for a custom + // serialization protocol. Will not be called during serialization. + protected void writeObject(ObjectOutputStream out) { + //... + } +} + +class NetRequest implements Serializable { + // GOOD: Signature for a custom deserialization implementation. + private void readObject(ObjectInputStream in) { + //... + } + + // GOOD: Signature for a custom serialization implementation. + private void writeObject(ObjectOutputStream out) { + //... + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Serialization/IncorrectSerializableMethods.qhelp b/java/ql/src/Likely Bugs/Serialization/IncorrectSerializableMethods.qhelp new file mode 100644 index 00000000000..fbb1f054105 --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/IncorrectSerializableMethods.qhelp @@ -0,0 +1,47 @@ + + + + + +

    +A serializable object that defines its own serialization protocol using the methods +readObject and writeObject must use the signature that is expected by the +Java serialization framework. Otherwise, the default serialization mechanism is used. +

    + +
    + +

    +Make sure that the signatures of readObject and writeObject on +serializable classes use these exact signatures: +

    + + + +
    + + +

    In the following example, WrongNetRequest defines readObject and +writeObject using the wrong signatures. However, NetRequest defines them +correctly.

    + + + +
    + + + +
  • + Java API Documentation: + Serializable. +
  • +
  • + Oracle Technology Network: + Discover the secrets of the Java Serialization API. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Serialization/IncorrectSerializableMethods.ql b/java/ql/src/Likely Bugs/Serialization/IncorrectSerializableMethods.ql new file mode 100644 index 00000000000..da1d442987d --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/IncorrectSerializableMethods.ql @@ -0,0 +1,22 @@ +/** + * @name Serialization methods do not match required signature + * @description A serialized class that implements 'readObject' or 'writeObject' but does not use + * the correct signatures causes the default serialization mechanism to be used. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/wrong-object-serialization-signature + * @tags reliability + * maintainability + * language-features + */ +import java + +from Method m, TypeSerializable serializable +where + m.getDeclaringType().hasSupertype+(serializable) and + m.getNumberOfParameters() = 1 and + m.getAParameter().getType().(RefType).hasQualifiedName("java.io", "ObjectOutputStream") and + (m.hasName("readObject") or m.hasName("writeObject")) and + not m.isPrivate() +select m, "readObject and writeObject should be private methods." diff --git a/java/ql/src/Likely Bugs/Serialization/IncorrectSerializableMethodsSig.java b/java/ql/src/Likely Bugs/Serialization/IncorrectSerializableMethodsSig.java new file mode 100644 index 00000000000..fca8fd6f558 --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/IncorrectSerializableMethodsSig.java @@ -0,0 +1,4 @@ +private void readObject(java.io.ObjectInputStream in) + throws IOException, ClassNotFoundException; +private void writeObject(java.io.ObjectOutputStream out) + throws IOException; \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorOnExternalizable.java b/java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorOnExternalizable.java new file mode 100644 index 00000000000..fc297ce1788 --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorOnExternalizable.java @@ -0,0 +1,37 @@ +class WrongMemo implements Externalizable { + private String memo; + + // BAD: No public no-argument constructor is defined. Deserializing this object + // causes an 'InvalidClassException'. + + public WrongMemo(String memo) { + this.memo = memo; + } + + public void writeExternal(ObjectOutput arg0) throws IOException { + //... + } + public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + //... + } +} + +class Memo implements Externalizable { + private String memo; + + // GOOD: Declare a public no-argument constructor, which is used by the + // serialization framework when the object is deserialized. + public Memo() { + } + + public Memo(String memo) { + this.memo = memo; + } + + public void writeExternal(ObjectOutput out) throws IOException { + //... + } + public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + //... + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorOnExternalizable.qhelp b/java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorOnExternalizable.qhelp new file mode 100644 index 00000000000..8dbfbcb97b1 --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorOnExternalizable.qhelp @@ -0,0 +1,49 @@ + + + + + +

    +A class that implements java.io.Externalizable must have a public no-argument +constructor. The constructor is used by the Java serialization framework when it creates the object +during deserialization. If the class does not define such a constructor, the Java +serialization framework throws an InvalidClassException. +

    + +

    +The Java Development Kit API documentation for Externalizable states: +

    + +
    +

    When an Externalizable object is reconstructed, an instance is created using the public no-arg constructor, +then the readExternal method called.

    +
    + +
    + +

    +Make sure that externalizable classes always have a no-argument constructor. +

    + +
    + +

    In the following example, WrongMemo does not declare a public no-argument constructor. +When the Java serialization framework tries to deserialize the object, an InvalidClassException +is thrown. However, Memo does declare a public no-argument constructor, so that the +object is deserialized successfully.

    + + +
    + + + +
  • + Java API Documentation: + Externalizable. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorOnExternalizable.ql b/java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorOnExternalizable.ql new file mode 100644 index 00000000000..43e9bab3b5d --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorOnExternalizable.ql @@ -0,0 +1,24 @@ +/** + * @name Externalizable but no public no-argument constructor + * @description A class that implements 'Externalizable' but does not have a public no-argument + * constructor causes an 'InvalidClassException' to be thrown. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/missing-no-arg-constructor-on-externalizable + * @tags reliability + * maintainability + * language-features + */ +import java + +from Class extern, Interface externalizable +where + externalizable.hasQualifiedName("java.io", "Externalizable") and + extern.hasSupertype+(externalizable) and + not extern.isAbstract() and + not exists(Constructor c | c = extern.getAConstructor() | + c.hasNoParameters() and + c.isPublic() + ) +select extern, "This class is Externalizable but has no public no-argument constructor." diff --git a/java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorsOnSerializable.java b/java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorsOnSerializable.java new file mode 100644 index 00000000000..d9161f38710 --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorsOnSerializable.java @@ -0,0 +1,42 @@ +class WrongItem { + private String name; + + // BAD: This class does not have a no-argument constructor, and throws an + // 'InvalidClassException' at runtime. + + public WrongItem(String name) { + this.name = name; + } +} + +class WrongSubItem extends WrongItem implements Serializable { + public WrongSubItem() { + super(null); + } + + public WrongSubItem(String name) { + super(name); + } +} + +class Item { + private String name; + + // GOOD: This class declares a no-argument constructor, which allows serializable + // subclasses to be deserialized without error. + public Item() {} + + public Item(String name) { + this.name = name; + } +} + +class SubItem extends Item implements Serializable { + public SubItem() { + super(null); + } + + public SubItem(String name) { + super(name); + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorsOnSerializable.qhelp b/java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorsOnSerializable.qhelp new file mode 100644 index 00000000000..72fabede0da --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorsOnSerializable.qhelp @@ -0,0 +1,51 @@ + + + + + +

    +A serializable class that is a subclass of a non-serializable class cannot be deserialized if its +superclass does not declare a no-argument constructor. The Java serialization framework uses the no-argument constructor when it initializes the +object instance that is created during deserialization. Deserialization fails with an InvalidClassException if +its superclass does not declare a no-argument constructor. +

    + +

    The Java Development Kit API documentation states:

    + +
    +

    To allow subtypes of non-serializable classes to be serialized, the subtype may assume responsibility for saving and restoring the state of +the supertype's public, protected, and (if accessible) package fields. The subtype may assume this responsibility only if the class it +extends has an accessible no-arg constructor to initialize the class's state. It is an error to declare a class Serializable if this +is not the case. The error will be detected at runtime.

    +
    + +
    + +

    Make sure that every non-serializable class that is extended by a serializable class has a no-argument constructor.

    + +
    + + +

    In the following example, the class WrongSubItem cannot be deserialized because its +superclass WrongItem does not declare a no-argument constructor. However, the class +SubItem can be serialized because it declares a no-argument constructor.

    + + +
    + + + +
  • +Java API Documentation: +Serializable. +
  • +
  • + J. Bloch, Effective Java (second edition), Item 74. + Addison-Wesley, 2008. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorsOnSerializable.ql b/java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorsOnSerializable.ql new file mode 100644 index 00000000000..66ff53f6950 --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/MissingVoidConstructorsOnSerializable.ql @@ -0,0 +1,30 @@ +/** + * @name Serializable but no void constructor + * @description A non-serializable, immediate superclass of a serializable class that does not + * itself declare an accessible, no-argument constructor causes deserialization to + * fail. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/missing-no-arg-constructor-on-serializable + * @tags reliability + * maintainability + * language-features + */ +import java + +from Class serial, Class nonserial, TypeSerializable serializable +where + serial.hasSupertype(nonserial) and + serial.hasSupertype+(serializable) and + not nonserial.hasSupertype+(serializable) and + not exists(Constructor c | + c = nonserial.getSourceDeclaration().getAConstructor() and + c.hasNoParameters() and + not(c.isPrivate()) + ) and + serial.fromSource() +select serial, + "This class is serializable, but its non-serializable " + + "super-class $@ does not declare a no-argument constructor.", + nonserial, nonserial.getName() diff --git a/java/ql/src/Likely Bugs/Serialization/NonSerializableComparator.java b/java/ql/src/Likely Bugs/Serialization/NonSerializableComparator.java new file mode 100644 index 00000000000..1617440e762 --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/NonSerializableComparator.java @@ -0,0 +1,16 @@ +// BAD: This is not serializable, and throws a 'java.io.NotSerializableException' +// when used in a serializable sorted collection. +class WrongComparator implements Comparator { + public int compare(String o1, String o2) { + return o1.compareTo(o2); + } +} + +// GOOD: This is serializable, and can be used in collections that are meant to be serialized. +class StringComparator implements Comparator, Serializable { + private static final long serialVersionUID = -5972458403679726498L; + + public int compare(String arg0, String arg1) { + return arg0.compareTo(arg1); + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Serialization/NonSerializableComparator.qhelp b/java/ql/src/Likely Bugs/Serialization/NonSerializableComparator.qhelp new file mode 100644 index 00000000000..eeb13572f56 --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/NonSerializableComparator.qhelp @@ -0,0 +1,57 @@ + + + + + +

    +A class that implements java.util.Comparator and is used to +construct a sorted collection needs to be serializable. +An ordered collection (such as a java.util.TreeMap) that is constructed +using a comparator serializes successfully only if the comparator is serializable. +

    + +

    +The Collections in the Java Standard Library that require a comparator +(TreeSet, TreeMap, PriorityQueue) all call +ObjectOutputStream.defaultWriteObject, which tries to serialize every +non-static, non-transient field in the class. As the comparator +is stored in a field in these collections, the attempt to serialize a non-serializable +comparator throws a java.io.NotSerializableException. +

    + +
    + +

    +Comparators should be serializable if they are used in sorted collections that +may be serialized. In most cases, simply changing the comparator so it also implements +Serializable is enough. Comparators that have +internal state may require additional changes (for example, custom writeObject and +readObject methods). In these cases, it is best to follow general best practices +for serializable objects (see references below). +

    + +
    + + +

    In the following example, WrongComparator is not serializable because it does not +implement Serializable. However, StringComparator is serializable because +it does implement Serializable. + +

    + +
    + + + +
  • +Java API Documentation: +Comparator, +ObjectOutputStream, +Serializable. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Serialization/NonSerializableComparator.ql b/java/ql/src/Likely Bugs/Serialization/NonSerializableComparator.ql new file mode 100644 index 00000000000..3adf36b3f3b --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/NonSerializableComparator.ql @@ -0,0 +1,50 @@ +/** + * @name Non-serializable comparator + * @description A comparator that is passed to an ordered collection (for example, a treemap) must be + * serializable, otherwise the collection fails to serialize at run-time. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/non-serializable-comparator + * @tags reliability + * maintainability + * language-features + */ +import java + +predicate nonSerializableComparator(Class c) { + exists(TypeSerializable serializable, GenericInterface comparator | + comparator.hasQualifiedName("java.util", "Comparator") and + c.getASourceSupertype+() = comparator and + not c.getASourceSupertype+() = serializable and + c.fromSource() + ) +} + +predicate sortedCollectionBaseType(RefType t) { + t.hasName("SortedSet") or + t.hasName("SortedMap") or + t.hasName("PriorityQueue") +} + +predicate sortedCollectionType(RefType t) { + sortedCollectionBaseType(t.getASupertype*().getSourceDeclaration()) +} + +string nameFor(Class c) { + nonSerializableComparator(c) and + ( + (c instanceof AnonymousClass and result = "This comparator") or + (not c instanceof AnonymousClass and result = c.getName()) + ) +} + +from Class c, Expr arg, ClassInstanceExpr cie +where + nonSerializableComparator(c) and + c = arg.getType() and + arg = cie.getAnArgument() and + sortedCollectionType(cie.getType()) +select arg, + nameFor(c) + " is not serializable, so should not be used as the comparator in a " + + cie.getType().getName() + "." diff --git a/java/ql/src/Likely Bugs/Serialization/NonSerializableField.java b/java/ql/src/Likely Bugs/Serialization/NonSerializableField.java new file mode 100644 index 00000000000..999e0dc207a --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/NonSerializableField.java @@ -0,0 +1,27 @@ +class DerivedFactors { // Class that contains derived values computed from entries in a + private Number efficiency; // performance record + private Number costPerItem; + private Number profitPerItem; + ... +} + +class WrongPerformanceRecord implements Serializable { + private String unitId; + private Number dailyThroughput; + private Number dailyCost; + private DerivedFactors factors; // BAD: 'DerivedFactors' is not serializable + // but is in a serializable class. This + // causes a 'java.io.NotSerializableException' + // when 'WrongPerformanceRecord' is serialized. + ... +} + +class PerformanceRecord implements Serializable { + private String unitId; + private Number dailyThroughput; + private Number dailyCost; + transient private DerivedFactors factors; // GOOD: 'DerivedFactors' is declared + // 'transient' so it does not contribute to the + // serializable state of 'PerformanceRecord'. + ... +} diff --git a/java/ql/src/Likely Bugs/Serialization/NonSerializableField.qhelp b/java/ql/src/Likely Bugs/Serialization/NonSerializableField.qhelp new file mode 100644 index 00000000000..81d490f1c44 --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/NonSerializableField.qhelp @@ -0,0 +1,76 @@ + + + + + +

    +If a serializable class is serialized using the default Java serialization mechanism, +each non-static, non-transient field in the class must also be serializable. +Otherwise, the class generates a java.io.NotSerializableException as its fields are written +out by ObjectOutputStream.writeObject. +

    + +

    +As an exception, classes that define their own readObject and writeObject +methods can have fields that are not themselves serializable. The readObject and +writeObject methods are responsible for encoding any state in those fields that needs +to be serialized. +

    + +
    + +

    +To avoid causing a NotSerializableException, do one of the following:

    +
      +
    • + Mark the field as transient : Marking the field as transient makes the + serialization mechanism skip the field. Before doing this, make sure that the field is not really intended + to be part of the persistent state of the object. +
    • +
    • + Define custom readObject and writeObject methods for the Serializable class : + Explicitly defining the readObject and writeObject methods enables you to choose which fields + to read from, or write to, an object stream during serialization. +
    • +
    • + Make the type of the field Serializable : If the field is part of the object's persistent state and you wish + to use Java's default serialization mechanism, the type of the field must implement Serializable. When choosing this option, + make sure that you follow best practices for serialization. +
    • +
    + +
    +
    +

    In the following example, WrongPerformanceRecord contains a field factors +that is not serializable but is in a serializable class. This causes a java.io.NotSerializableException +when the field is written out by writeObject. However, PerformanceRecord +contains a field factors that is marked as transient, so that the serialization +mechanism skips the field. This means that a correctly serialized record is output by writeObject.

    + + +
    +
    +

    In this second example, WrongPair takes two generic parameters L and R. +The class itself is serializable, but users of this class are not forced to pass serializable objects to its +constructor, which could lead to problems during serialization. The solution is to set upper type bounds for the +parameters, to force the user to supply only serializable objects. +A similar example is the WrongEvent class, which takes a weakly typed eventData object. +A better solution is to force the user to supply an object whose class implements the Serializable +interface.

    + + +
    + + + +
  • + Java API Documentation: + Serializable, + ObjectOutputStream. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Serialization/NonSerializableField.ql b/java/ql/src/Likely Bugs/Serialization/NonSerializableField.ql new file mode 100644 index 00000000000..9e69bc59a86 --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/NonSerializableField.ql @@ -0,0 +1,98 @@ +/** + * @name Non-serializable field + * @description A non-transient field in a serializable class must also be serializable + * otherwise it causes the class to fail to serialize with a 'NotSerializableException'. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/non-serializable-field + * @tags reliability + * maintainability + * language-features + */ +import java +import semmle.code.java.JDKAnnotations +import semmle.code.java.Collections +import semmle.code.java.Maps +import semmle.code.java.frameworks.javaee.ejb.EJB + +predicate externalizable(Interface interface) { + interface.hasQualifiedName("java.io", "Externalizable") +} + +predicate serializableOrExternalizable(Interface interface) { + externalizable(interface) or + interface instanceof TypeSerializable +} + +predicate collectionOrMapType(RefType t) { + t instanceof CollectionType or t instanceof MapType +} + +predicate serializableType(RefType t) { + exists(RefType sup | sup = t.getASupertype*() | serializableOrExternalizable(sup)) or + ( + // Collection interfaces are not serializable, but their implementations are + // likely to be. + collectionOrMapType(t) and + forall(RefType param | param = t.(ParameterizedType).getATypeArgument() | serializableType(param)) + ) or + exists(BoundedType bt | bt = t | serializableType(bt.getUpperBoundType())) +} + +RefType reasonForNonSerializableCollection(ParameterizedType par){ + collectionOrMapType(par) and + result = par.getATypeArgument() and + not serializableType(result) +} + +string nonSerialReason(RefType t) { + not serializableType(t) and + if exists(reasonForNonSerializableCollection(t)) then + result = reasonForNonSerializableCollection(t).getName() + " is not serializable" + else + result = t.getName() + " is not serializable" +} + +predicate exceptions(Class c, Field f){ + f.getDeclaringType() = c and ( + // `Serializable` objects with custom `readObject` or `writeObject` methods + // may write out the "non-serializable" fields in a different way. + c.declaresMethod("readObject") or + c.declaresMethod("writeObject") or + + // Exclude classes with suppressed warnings. + c.suppressesWarningsAbout("serial") or + + // Exclude anonymous classes whose `ClassInstanceExpr` is assigned to + // a variable on which serialization warnings are suppressed. + exists(Variable v | + v.getAnAssignedValue() = c.(AnonymousClass).getClassInstanceExpr() and + v.suppressesWarningsAbout("serial") + ) or + + f.isTransient() or + f.isStatic() or + + // Classes that implement `Externalizable` completely take over control during serialization. + externalizable(c.getASupertype+()) or + + // Stateless session beans are not normally serialized during their usual life-cycle + // but are forced by their expected supertype to be serializable. + // Arguably, warnings for their non-serializable fields can therefore be suppressed in practice. + c instanceof StatelessSessionEJB or + + // Enum types are serialized by name, so it doesn't matter if they have non-serializable fields. + c instanceof EnumType + ) +} + +from Class c, Field f, string reason +where + c.fromSource() and + c.getASupertype+() instanceof TypeSerializable and + f.getDeclaringType() = c and + not exceptions(c, f) and + reason = nonSerialReason(f.getType()) +select f, "This field is in a serializable class, " + + " but is not serializable itself because " + reason + "." diff --git a/java/ql/src/Likely Bugs/Serialization/NonSerializableFieldTooGeneral.java b/java/ql/src/Likely Bugs/Serialization/NonSerializableFieldTooGeneral.java new file mode 100644 index 00000000000..b2d51cf73e8 --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/NonSerializableFieldTooGeneral.java @@ -0,0 +1,29 @@ +class WrongPair implements Serializable{ + private final L left; // BAD + private final R right; // BAD: L and R are not guaranteed to be serializable + + public WrongPair(L left, R right){ ... } + + ... +} + +class Pair implements Serializable{ + private final L left; // GOOD: L and R must implement Serializable + private final R right; + + public Pair(L left, R right){ ... } + + ... +} + +class WrongEvent implements Serializable{ + private Object eventData; // BAD: Type is too general. + + public WrongEvent(Object eventData){ ... } +} + +class Event implements Serializable{ + private Serializable eventData; // GOOD: Force the user to supply only serializable data + + public Event(Serializable eventData){ ... } +} diff --git a/java/ql/src/Likely Bugs/Serialization/NonSerializableInnerClass.java b/java/ql/src/Likely Bugs/Serialization/NonSerializableInnerClass.java new file mode 100644 index 00000000000..d2d44110812 --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/NonSerializableInnerClass.java @@ -0,0 +1,33 @@ +class NonSerializableServer { + + // BAD: The following class is serializable, but the enclosing class + // 'NonSerializableServer' is not. Serializing an instance of 'WrongSession' + // causes a 'java.io.NotSerializableException'. + class WrongSession implements Serializable { + private static final long serialVersionUID = 8970783971992397218L; + private int id; + private String user; + + WrongSession(int id, String user) { /*...*/ } + } + + public WrongSession getNewSession(String user) { + return new WrongSession(newId(), user); + } +} + +class Server { + + // GOOD: The following class can be correctly serialized because it is static. + static class Session implements Serializable { + private static final long serialVersionUID = 1065454318648105638L; + private int id; + private String user; + + Session(int id, String user) { /*...*/ } + } + + public Session getNewSession(String user) { + return new Session(newId(), user); + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Serialization/NonSerializableInnerClass.qhelp b/java/ql/src/Likely Bugs/Serialization/NonSerializableInnerClass.qhelp new file mode 100644 index 00000000000..0084fcf10eb --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/NonSerializableInnerClass.qhelp @@ -0,0 +1,65 @@ + + + + + +

    +Non-static nested classes that implement Serializable must be defined in an enclosing class +that is also serializable. Non-static nested classes retain an implicit reference to +an instance of their enclosing class. If the enclosing class is not serializable, the Java +serialization mechanism fails with a java.io.NotSerializableException. +

    + +
    + +

    +To avoid causing a NotSerializableException, do one of the following: +

    +
      +
    • + Declare the nested class as static : If the nested class does not use any of the non-static + fields or methods of the enclosing class, it is best to declare it static. This removes the implicit reference to an instance of the enclosing + class, and has the additional effect of breaking an unnecessary dependency between the two classes. A similar solution is to + turn the nested class into a separate top-level class. +
    • +
    • + Make the enclosing class implement Serializable : However, this is not recommended because the implementation of inner classes may be compiler-specific, and + serializing an inner class can result in non-portability across compilers. The Java Serialization Specification states: +

      + Serialization of inner classes (i.e., nested classes that are not static member classes), including local and anonymous classes, + is strongly discouraged for several reasons. Because inner classes declared in non-static contexts contain implicit non-transient + references to enclosing class instances, serializing such an inner class instance will result in serialization of its associated + outer class instance as well. Synthetic fields generated by javac (or other Java(TM) compilers) to implement inner classes are + implementation dependent and may vary between compilers; differences in such fields can disrupt compatibility as well as result + in conflicting default serialVersionUID values. The names assigned to local and anonymous inner classes are also implementation + dependent and may differ between compilers. +

      +
      +
    • +
    + +
    + + +

    In the following example, the class WrongSession cannot be serialized without +causing a NotSerializableException, because it is enclosed by a non-serializable class. +However, the class Session can be serialized because it is declared as +static.

    + + + +
    + + + +
  • + Java 6 Object Serialization Specification: + 1.10 The Serializable Interface, + 2.1 The ObjectOutputStream Class. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Serialization/NonSerializableInnerClass.ql b/java/ql/src/Likely Bugs/Serialization/NonSerializableInnerClass.ql new file mode 100644 index 00000000000..e9fdbc0c806 --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/NonSerializableInnerClass.ql @@ -0,0 +1,98 @@ +/** + * @name Serializable inner class of non-serializable class + * @description A class that is serializable with an enclosing class that is not serializable + * causes serialization to fail. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/non-serializable-inner-class + * @tags reliability + * maintainability + * language-features + */ +import java +import semmle.code.java.JDKAnnotations + +predicate isSerializable(RefType t) { + exists(TypeSerializable ts | ts = t.getASupertype*()) +} + +predicate withinStaticContext(NestedClass c) { + c.isStatic() or + c.(AnonymousClass).getClassInstanceExpr().getEnclosingCallable().isStatic() // JLS 15.9.2 +} + +RefType enclosingInstanceType(Class inner){ + not withinStaticContext(inner) and + result = inner.(NestedClass).getEnclosingType() +} + +predicate castTo(ClassInstanceExpr cie, RefType to){ + exists(LocalVariableDeclExpr lvd | lvd.getInit() = cie | + to = lvd.getType() + ) or + exists(Assignment a | a.getSource() = cie | + to = a.getType() + ) or + exists(Call call, int n | call.getArgument(n) = cie | + to = call.getCallee().getParameterType(n) + ) or + exists(ReturnStmt ret | ret.getResult() = cie | + to = ret.getEnclosingCallable().getReturnType() + ) or + exists(ArrayCreationExpr ace | ace.getInit().getAnInit() = cie | + to = ace.getType().(Array).getComponentType() + ) +} + +predicate exceptions(NestedClass inner){ + inner instanceof AnonymousClass or + + // Serializable objects with custom `readObject` or `writeObject` methods may write out + // the "non-serializable" fields in a different way. + inner.declaresMethod("readObject") or + inner.declaresMethod("writeObject") or + + // Exclude cases where serialization warnings are deliberately suppressed. + inner.suppressesWarningsAbout("serial") or + + // The class `inner` is a local class or non-public member class and + // all its instance expressions are cast to non-serializable types. + ( + (inner instanceof LocalClass or not inner.isPublic()) and + forall(ClassInstanceExpr cie, RefType target | + cie.getConstructedType() = inner and castTo(cie, target) + | + not isSerializable(target) + ) and + // Exception 1: the expression is used as an argument to `writeObject()`. + not exists(Call writeCall, ClassInstanceExpr cie | cie.getConstructedType() = inner | + writeCall.getCallee().hasName("writeObject") and + writeCall.getAnArgument() = cie + ) and + // Exception 2: the expression is thrown as an exception (exceptions should be serializable + // due to use cases such as remote procedure calls, logging, etc.) + not exists(ThrowStmt ts, ClassInstanceExpr cie | + cie.getConstructedType() = inner and + ts.getExpr() = cie + ) and + // Exception 3: if the programmer added a `serialVersionUID`, we interpret that + // as an intent to make the class serializable. + not exists(Field f | f.getDeclaringType() = inner | f.hasName("serialVersionUID")) + ) +} + +from NestedClass inner, Class outer, string advice +where + inner.fromSource() and + isSerializable(inner) and + outer = enclosingInstanceType+(inner) and + not isSerializable(outer) and + not exceptions(inner) and + ( + if (inner instanceof LocalClass) then + advice = "Consider implementing readObject() and writeObject()." + else + advice = "Consider making the class static or implementing readObject() and writeObject()." + ) +select inner, "Serializable inner class of non-serializable class $@. " + advice, outer, outer.getName() diff --git a/java/ql/src/Likely Bugs/Serialization/ReadResolveObject.java b/java/ql/src/Likely Bugs/Serialization/ReadResolveObject.java new file mode 100644 index 00000000000..cba64daf49c --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/ReadResolveObject.java @@ -0,0 +1,40 @@ +class FalseSingleton implements Serializable { + private static final long serialVersionUID = -7480651116825504381L; + private static FalseSingleton instance; + + private FalseSingleton() {} + + public static FalseSingleton getInstance() { + if (instance == null) { + instance = new FalseSingleton(); + } + return instance; + } + + // BAD: Signature of 'readResolve' does not match the exact signature that is expected + // (that is, it does not return 'java.lang.Object'). + public FalseSingleton readResolve() throws ObjectStreamException { + return FalseSingleton.getInstance(); + } +} + +class Singleton implements Serializable { + private static final long serialVersionUID = -7480651116825504381L; + private static Singleton instance; + + private Singleton() {} + + public static Singleton getInstance() { + if (instance == null) { + instance = new Singleton(); + } + return instance; + } + + // GOOD: Signature of 'readResolve' matches the exact signature that is expected. + // It replaces the singleton that is read from a stream with an instance of 'Singleton', + // instead of creating a new singleton. + private Object readResolve() throws ObjectStreamException { + return Singleton.getInstance(); + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Serialization/ReadResolveObject.qhelp b/java/ql/src/Likely Bugs/Serialization/ReadResolveObject.qhelp new file mode 100644 index 00000000000..96f8b3b3f30 --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/ReadResolveObject.qhelp @@ -0,0 +1,65 @@ + + + + + +

    +If a class uses the readResolve method to specify a replacement object instance when the object is read from a stream, +ensure that the signature of readResolve is exactly what the Java serialization mechanism +expects.

    + +
    + +

    +Ensure that the signature of the readResolve method in the class matches the expected signature:

    +

    + + ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException; + +

    + +

    Note that the method must return a java.lang.Object. +

    + +

    +If readResolve is used for instance control of a serializable singleton, +(that is, to make sure that deserializing a singleton class does not result in another instance of the singleton) +it may be possible to use an enum with a single element instead. The Java serialization specification +explicitly ensures that deserializing an enum does not create a new instance. +(For details about this technique, see [Bloch].) +

    + +
    + + +

    In the following example, FalseSingleton.readResolve has the wrong signature, which +causes deserialization to create a new instance of the singleton. However, Singleton.readResolve +has the correct signature, which means that deserialization does not result in another instance of +the singleton.

    + + + +
    + + + +
  • +Java API Documentation: +Serializable. +
  • +
  • +Java 6 Object Serialization Specification: +3.7 The readResolve Method, +1.12 Serialization of Enum Constants. +
  • +
  • + J. Bloch, Effective Java (second edition), + Item 77. + Addison-Wesley, 2008. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Serialization/ReadResolveObject.ql b/java/ql/src/Likely Bugs/Serialization/ReadResolveObject.ql new file mode 100644 index 00000000000..63d275bfb78 --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/ReadResolveObject.ql @@ -0,0 +1,25 @@ +/** + * @name ReadResolve must have Object return type, not void + * @description An implementation of 'readResolve' that does not have the signature that is expected + * by the Java serialization framework is not recognized by the serialization + * mechanism. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/wrong-readresolve-signature + * @tags reliability + * maintainability + * language-features + */ +import java + +from TypeSerializable serializable, Class c, Method m +where + c.hasSupertype+(serializable) and + m.getDeclaringType() = c and + m.hasName("readResolve") and + m.hasNoParameters() and + not m.getReturnType() instanceof TypeObject +select m, "The method " + m.getName() + + " must be declared with a return type of Object rather than " + + m.getReturnType().getName() + "." diff --git a/java/ql/src/Likely Bugs/Serialization/TransientNotSerializable.java b/java/ql/src/Likely Bugs/Serialization/TransientNotSerializable.java new file mode 100644 index 00000000000..783ed4f4181 --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/TransientNotSerializable.java @@ -0,0 +1,12 @@ +class State { + // The 'transient' modifier has no effect here because + // the 'State' class does not implement 'Serializable'. + private transient int[] stateData; +} + +class PersistentState implements Serializable { + private int[] stateData; + // The 'transient' modifier indicates that this field is not part of + // the persistent state and should therefore not be serialized. + private transient int[] cachedComputedData; +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Serialization/TransientNotSerializable.qhelp b/java/ql/src/Likely Bugs/Serialization/TransientNotSerializable.qhelp new file mode 100644 index 00000000000..395336b95c1 --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/TransientNotSerializable.qhelp @@ -0,0 +1,48 @@ + + + + + +

    +The transient modifier is used to identify fields that are not part of the persistent state of the class. As such, it only has an +effect if the class is serializable, and has no purpose in a non-serializable class. +

    + +

    +A field that is marked transient in a non-serializable class is likely to be a leftover from a time when the class +was serializable. +

    + +
    + +

    +If the class is non-serializable, leave out the transient modifier. Otherwise, +use the modifier, and ensure that the class (or a relevant supertype) implements Serializable. +

    + +
    + + +

    The following example shows two fields that are declared transient. The modifier only has an effect +in the class that implements Serializable.

    + + + +
    + + + +
  • + Java Language Specification, 3rd Ed: + 8.3.1.3 transient Fields. +
  • +
  • + Java 6 Object Serialization Specification: + 1.5 Defining Serializable Fields for a Class. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Serialization/TransientNotSerializable.ql b/java/ql/src/Likely Bugs/Serialization/TransientNotSerializable.ql new file mode 100644 index 00000000000..6f6f2b71189 --- /dev/null +++ b/java/ql/src/Likely Bugs/Serialization/TransientNotSerializable.ql @@ -0,0 +1,20 @@ +/** + * @name Transient field in non-serializable class + * @description Using the 'transient' field modifier in non-serializable classes has no effect. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/transient-not-serializable + * @tags reliability + * maintainability + * language-features + */ +import java + +from TypeSerializable serializable, Class c, Field f +where + not c.hasSupertype+(serializable) and + f.getDeclaringType() = c and + f.isTransient() +select + f, "The field " + f.getName() + " is transient but " + c.getName() + " is not Serializable." diff --git a/java/ql/src/Likely Bugs/Statements/Chaining.qll b/java/ql/src/Likely Bugs/Statements/Chaining.qll new file mode 100644 index 00000000000..2cd251c18ef --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/Chaining.qll @@ -0,0 +1,63 @@ +import java + +/** + * Find methods that always return `this`, or interface methods that are + * overridden by such methods. + * + * For native or compiled methods (which have no body), we approximate this by + * requiring that the method returns a subtype of its declaring type, is not + * declared in an immutable type, and that every method overriding it is also + * designed for chaining. + */ +predicate designedForChaining(Method m) { + not nonChaining(m) +} + +private predicate nonChaining(Method m) { + // The method has a body, and at least one of the return values is not suitable for chaining. + exists(ReturnStmt ret | ret.getEnclosingCallable() = m | nonChainingReturn(m, ret)) + or + ( + // The method has no body, and is not chaining because ... + not exists(m.getBody()) and + ( + // ... it has the wrong return type, ... + not hasSubtype*(m.getReturnType(), m.getDeclaringType()) or + // ... it is defined on an immutable type, or ... + m.getDeclaringType() instanceof ImmutableType or + // ... it has an override that is non-chaining. + exists(Method override | override.overrides(m) | nonChaining(override)) + ) + ) +} + +private predicate nonChainingReturn(Method m, ReturnStmt ret) { + // The wrong `this` is returned. + ( + ret.getResult() instanceof ThisAccess and + ret.getResult().getType() != m.getDeclaringType() + ) or + // A method call to the wrong method is returned. + ( + ret.getResult() instanceof MethodAccess and + exists(MethodAccess delegateCall, Method delegate | + delegateCall = ret.getResult() and + delegate = delegateCall.getMethod() + | + delegate.getDeclaringType() != m.getDeclaringType() or + delegate.isStatic() or + not hasSubtype*(m.getReturnType(), delegate.getReturnType()) or + // A method on the wrong object is called. + not ( + delegateCall.getQualifier().getProperExpr() instanceof ThisAccess or + not exists(delegateCall.getQualifier()) + ) or + nonChaining(delegate) + ) + ) or + // Something else is returned. + not ( + ret.getResult() instanceof ThisAccess or + ret.getResult() instanceof MethodAccess + ) +} diff --git a/java/ql/src/Likely Bugs/Statements/EmptyBlock.java b/java/ql/src/Likely Bugs/Statements/EmptyBlock.java new file mode 100644 index 00000000000..f67f1ffe21e --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/EmptyBlock.java @@ -0,0 +1,10 @@ +public class Parser +{ + public void parse(String input) { + int pos = 0; + // ... + // AVOID: Empty block + while (input.charAt(pos++) != '=') { } + // ... + } +} diff --git a/java/ql/src/Likely Bugs/Statements/EmptyBlock.qhelp b/java/ql/src/Likely Bugs/Statements/EmptyBlock.qhelp new file mode 100644 index 00000000000..29d0b910e73 --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/EmptyBlock.qhelp @@ -0,0 +1,52 @@ + + + + + +

    An unexplained empty block or statement makes the +code less readable. It might also indicate missing code, a +misplaced semicolon, or a misplaced brace. For these reasons, it +should be avoided.

    + +
    + + +

    If a block is empty because some code is missing, add the code.

    +

    If an if statement has an empty then branch and a non-empty else branch, +it may be possible to negate the condition and move the statements of the else branch into the then branch.

    +

    If a block is deliberately empty, add a comment to explain why.

    + +
    + + +

    In the following example, the while loop has intentionally been left empty. +The purpose of the loop is to scan a String for the first occurrence of +the character '='. A programmer reading the code might not understand the reason +for the empty loop body, and think that something is missing, or perhaps even that the loop is useless. +Therefore it is a good practice to add a comment to an empty block explaining why it is empty.

    + + + +
    + + + +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • +
  • +Java Language Specification: +14.2 Blocks, +14.6 The Empty Statement, +14.9 The if Statement, +14.12 The while Statement, +14.13 The do Statement, +14.14 The for Statement. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Statements/EmptyBlock.ql b/java/ql/src/Likely Bugs/Statements/EmptyBlock.ql new file mode 100644 index 00000000000..796e4ef307c --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/EmptyBlock.ql @@ -0,0 +1,60 @@ +/** + * @name Empty branch of conditional, or empty loop body + * @description An undocumented empty block or statement hinders readability. It may also + * indicate incomplete code. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/empty-block + * @tags reliability + * readability + */ +import semmle.code.java.Statement + +/** A block without statements or comments. */ +private +Block emptyBlock() { + result.getNumStmt() = 0 and + result.getLocation().getNumberOfCommentLines() = 0 +} + +/** Auxiliary predicate: file and line of a comment. */ +private +predicate commentedLine(File file, int line) { + exists(JavadocText text, Location loc | + loc = text.getLocation() and + loc.getFile() = file and + loc.getStartLine() = line and + loc.getEndLine() = line) +} + +/** An uncommented empty statement */ +private +EmptyStmt emptyStmt() { + not commentedLine(result.getFile(), result.getLocation().getStartLine()) +} + +/** An empty statement or an empty block. */ +Stmt emptyBody() { + result = emptyBlock() or result = emptyStmt() +} + +/** + * Empty blocks or empty statements should not occur as immediate children of if-statements or loops. + * Empty blocks should not occur within other blocks. + */ +predicate blockParent(Stmt empty, string msg) +{ + empty = emptyBody() and + ( + (empty.getParent() instanceof IfStmt and msg = "The body of an if statement should not be empty.") or + (empty.getParent() instanceof LoopStmt and msg = "The body of a loop should not be empty.") or + (empty.getParent() instanceof Block and empty instanceof Block and msg = "This block should not be empty.") + ) +} + +from Stmt empty, string msg +where + empty = emptyBody() and + blockParent(empty, msg) +select empty, msg + " Typographical error or missing code?" diff --git a/java/ql/src/Likely Bugs/Statements/EmptySynchronizedBlock.qhelp b/java/ql/src/Likely Bugs/Statements/EmptySynchronizedBlock.qhelp new file mode 100644 index 00000000000..fcf83b44fdd --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/EmptySynchronizedBlock.qhelp @@ -0,0 +1,36 @@ + + + + + +

    Empty synchronized blocks suspend execution until a lock can be acquired, +which is then released immediately. This is unlikely to achieve the desired effect +and may indicate the presence of incomplete code or incorrect synchronization. It may also lead to +concurrency problems. +

    + +
    + + +

    Check which code needs to be synchronized. +Any code that requires synchronization on the given lock should be placed +within the synchronized block. +

    + +
    + + +
  • + The Java Language Specification: + The synchronized Statement. +
  • +
  • + The Java Tutorials: + Synchronization. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Statements/EmptySynchronizedBlock.ql b/java/ql/src/Likely Bugs/Statements/EmptySynchronizedBlock.ql new file mode 100644 index 00000000000..a6245e0296a --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/EmptySynchronizedBlock.ql @@ -0,0 +1,19 @@ +/** + * @name Empty synchronized block + * @description Empty synchronized blocks may indicate the presence of + * incomplete code or incorrect synchronization, and may lead to concurrency problems. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/empty-synchronized-block + * @tags reliability + * correctness + * concurrency + * language-features + * external/cwe/cwe-585 + */ +import java + +from SynchronizedStmt sync +where not exists(sync.getBlock().getAChild()) +select sync, "Empty synchronized block." diff --git a/java/ql/src/Likely Bugs/Statements/ImpossibleCast.qhelp b/java/ql/src/Likely Bugs/Statements/ImpossibleCast.qhelp new file mode 100644 index 00000000000..8f6d4140cad --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/ImpossibleCast.qhelp @@ -0,0 +1,43 @@ + + + + + +

    Some downcasts on arrays will fail at runtime. +An object a with dynamic type A[] cannot be cast to B[], +where B is a subtype of A, +even if all the elements of a can be cast to B. +

    + +
    + + +

    Ensure that the array creation expression constructs an array object of the right type.

    + +
    + + +

    The following example shows an assignment that throws a ClassCastException at runtime.

    + +String[] strs = (String[])new Object[]{ "hello", "world" }; + +

    To avoid the exception, a String array should be created instead.

    + +String[] strs = new String[]{ "hello", "world" }; + +
    + + + +
  • + The Java Language Specification: + Checked Casts at Run Time, + Reference Type Casting, + Subtyping among Array Types. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Statements/ImpossibleCast.ql b/java/ql/src/Likely Bugs/Statements/ImpossibleCast.ql new file mode 100644 index 00000000000..099e1d88b55 --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/ImpossibleCast.ql @@ -0,0 +1,112 @@ +/** + * @name Impossible array cast + * @description Trying to cast an array of a particular type as an array of a subtype causes a + * 'ClassCastException' at runtime. + * @kind problem + * @problem.severity error + * @precision low + * @id java/impossible-array-cast + * @tags reliability + * correctness + * logic + * external/cwe/cwe-704 + */ +import java + +/** + * A cast expression of the form `(T[])new S[...]`. + */ +class ArrayCast extends CastExpr { + ArrayCast() { + getExpr() instanceof ArrayCreationExpr and + getType() instanceof Array + } + + /** The type of the operand expression of this cast. */ + Array getSourceType() { + result = getExpr().getType() + } + + /** The result type of this cast. */ + Array getTargetType() { + result = getType() + } + + Type getSourceComponentType() { + result = getSourceType().getComponentType() + } + + Type getTargetComponentType() { + result = getTargetType().getComponentType() + } +} + +predicate uncheckedCastType(RefType t) { + t instanceof BoundedType or t instanceof ParameterizedType +} + +predicate castFlow(ArrayCast ce, Variable v) { + ce = v.getAnAssignedValue() or + exists(Variable mid | castFlow(ce, mid) and mid.getAnAccess() = v.getAnAssignedValue()) +} + +predicate returnedFrom(ArrayCast ce, Method m) { + exists(ReturnStmt ret | ret.getEnclosingCallable() = m | + ret.getResult() = ce + ) or + exists(Variable v | castFlow(ce, v) | returnedVariableFrom(v, m)) +} + +predicate returnedVariableFrom(Variable v, Method m) { + exists(ReturnStmt ret | ret.getResult() = v.getAnAccess() and ret.getEnclosingCallable() = m) +} + +predicate rawTypeConversion(RawType source, ParameterizedType target) { + target.getErasure() = source.getErasure() +} + +from ArrayCast ce, RefType target, RefType source, string message +where + target = ce.getTargetComponentType() and + source = ce.getSourceComponentType() and + target.hasSupertype+(source) and + not rawTypeConversion(source, target) and + ( + // No unchecked operations, so the cast would crash straight away. + ( + not uncheckedCastType(target) and + message = "Impossible downcast: the cast from " + source.getName() + "[] to " + + target.getName() + "[] will always fail with a ClassCastException." + ) + or + /* + * For unchecked operations, the crash would not occur at the cast site, + * but only if/when the value is assigned to a variable of different array type. + * This would require tracking the flow of values, but we focus on finding problematic + * APIs. We keep two cases: + * - An array that is actually returned from the (non-private) method, or + * - an array that is assigned to a field returned from another (non-private) method. + */ + ( + uncheckedCastType(target) and + returnedFrom(ce, ce.getEnclosingCallable()) and + ce.getEnclosingCallable().getReturnType().(Array).getElementType() = target and + not ce.getEnclosingCallable().isPrivate() and + message = + "Impossible downcast: this is returned by " + ce.getEnclosingCallable().getName() + + " as a value of type " + target.getName() + "[], but the array has type " + source.getName() + + "[]. Callers of " + ce.getEnclosingCallable().getName() + " may fail with a ClassCastException." + ) + or + exists(Method m, Variable v | + uncheckedCastType(target) and + castFlow(ce, v) and returnedVariableFrom(v, m) and + m.getReturnType().(Array).getElementType() = target and + not m.isPrivate() and + message = + "Impossible downcast: this is assigned to " + v.getName() + " which is returned by " + m + + " as a value of type " + target.getName() + "[], but the array has type " + source.getName() + + "[]. Callers of " + m.getName() + " may fail with a ClassCastException." + ) + ) +select ce, message diff --git a/java/ql/src/Likely Bugs/Statements/InconsistentCallOnResult.java b/java/ql/src/Likely Bugs/Statements/InconsistentCallOnResult.java new file mode 100644 index 00000000000..57e59b8ff5b --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/InconsistentCallOnResult.java @@ -0,0 +1,12 @@ +DataOutputStream outValue = null; +try { + outValue = writer.prepareAppendValue(6); + outValue.write("value0".getBytes()); +} +catch (IOException e) { +} +finally { + if (outValue != null) { + outValue.close(); + } +} diff --git a/java/ql/src/Likely Bugs/Statements/InconsistentCallOnResult.qhelp b/java/ql/src/Likely Bugs/Statements/InconsistentCallOnResult.qhelp new file mode 100644 index 00000000000..5903e3160e3 --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/InconsistentCallOnResult.qhelp @@ -0,0 +1,32 @@ + + + + + +

    If the same operation +(for example, free, delete, close) +is usually performed on the result of a method call, +any instances where it is not performed +may be misuses of the API, leading to resource leaks or other problems. +

    + +
    + + +

    Ensure that the same operation is performed on the result of all calls to a particular +method, if appropriate. +

    + +
    + + +

    In the following example of good usage, the result of the call to writer.prepareAppendValue is +assigned to outValue, and later close is called on outValue. +Any instances where close is not called may cause resource leaks.

    + + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Statements/InconsistentCallOnResult.ql b/java/ql/src/Likely Bugs/Statements/InconsistentCallOnResult.ql new file mode 100644 index 00000000000..53d15212868 --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/InconsistentCallOnResult.ql @@ -0,0 +1,109 @@ +/** + * @name Inconsistent operation on return value + * @description If the same operation is usually performed + * on the result of a method call, any cases where it + * is not performed may indicate resource leaks or other problems. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/inconsistent-call-on-result + * @tags reliability + * correctness + * external/cwe/cwe-252 + * statistical + * non-attributable + */ +import java +import Chaining + +predicate exclude(Method m) { + exists(string name | name = m.getName().toLowerCase() | + name.matches("get%") or + name.matches("is%") or + name.matches("has%") or + name.matches("add%") + ) +} + +/** + * The method access `otherCall` + * - is described by operation, + * - operates on `v`, + * - is different from `callToCheck`, and + * - is not a call to an excluded method. + */ +predicate checkExpr(MethodAccess callToCheck, MethodAccess otherCall, string operation, Variable v) { + not exclude(otherCall.getMethod()) and + v.getAnAssignedValue() = callToCheck and + otherCall != callToCheck and + otherCall.getMethod().getName() = operation and + (otherCall.getAnArgument() = getChainedAccess(v) or otherCall.getQualifier() = getChainedAccess(v)) +} + +/** + * Holds if `operation` is implicitly called on `v`, and `v` is assigned the result of `callToCheck`. + */ +predicate implicitCheckExpr(MethodAccess callToCheck, string operation, Variable v) { + exists(TryStmt try, LocalVariableDeclExpr decl | + try.getAResourceDecl().getAVariable() = decl and + decl.getVariable() = v and + decl.getInit() = callToCheck and + operation = "close" + ) +} + +/** + * Get all accesses to a variable, either directly or by a chain of method calls. + */ +Expr getChainedAccess(Variable v) { + result = v.getAnAccess() or + exists(MethodAccess chainedAccess | + chainedAccess.getQualifier() = getChainedAccess(v) + | + designedForChaining(chainedAccess.getMethod()) and result = chainedAccess) +} + +/** + * The result of `ma` and a call to a method named `operation` are both assigned to the same variable. + */ +predicate checkedFunctionCall(MethodAccess ma, string operation) { + relevantFunctionCall(ma, _) and + exists(Variable v | not v instanceof Field | + v.getAnAssignedValue() = ma and + (checkExpr(ma, _, operation, v) or implicitCheckExpr(ma, operation, v)) + ) +} + +/** + * The method access `ma` is a call to `m` where the result is assigned. + */ +predicate relevantFunctionCall(MethodAccess ma, Method m) { + ma.getMethod() = m and + exists(Variable v | v.getAnAssignedValue() = ma) and + not okToIgnore(ma) +} + +predicate okToIgnore(MethodAccess ma) { + not ma.getCompilationUnit().fromSource() +} + +predicate functionStats(Method m, string operation, int used, int total, int percentage) { + m.getReturnType() instanceof RefType and + // Calls to `m` where we also perform `operation`. + used = strictcount(MethodAccess ma | checkedFunctionCall(ma, operation) and m = ma.getMethod()) and + // Calls to `m`. + total = strictcount(MethodAccess ma | relevantFunctionCall(ma, m)) and + percentage = used * 100 / total +} + +from MethodAccess unchecked, Method m, string operation, int percent +where + relevantFunctionCall(unchecked, m) and + not checkedFunctionCall(unchecked, operation) and + functionStats(m, operation, _, _, percent) and + percent >= 90 and + not m.getName() = operation and + not unchecked.getEnclosingStmt().(ExprStmt).isFieldDecl() +select unchecked, "After " + percent.toString() + "% of calls to " + m.getName() + + " there is a call to " + operation + + " on the return value. The call may be missing in this case." diff --git a/java/ql/src/Likely Bugs/Statements/MissingEnumInSwitch.java b/java/ql/src/Likely Bugs/Statements/MissingEnumInSwitch.java new file mode 100644 index 00000000000..add9f1f59ba --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/MissingEnumInSwitch.java @@ -0,0 +1,15 @@ +enum Answer { YES, NO, MAYBE } + +class Optimist +{ + Answer interpret(Answer answer) { + switch (answer) { + case MAYBE: + return Answer.YES; + case NO: + return Answer.MAYBE; + // Missing case for 'YES' + } + throw new RuntimeException("uncaught case: " + answer); + } +} diff --git a/java/ql/src/Likely Bugs/Statements/MissingEnumInSwitch.qhelp b/java/ql/src/Likely Bugs/Statements/MissingEnumInSwitch.qhelp new file mode 100644 index 00000000000..bcdd36377f1 --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/MissingEnumInSwitch.qhelp @@ -0,0 +1,47 @@ + + + + + +

    A switch statement that is based on a variable with an enum type should either have a default case +or handle all possible constants of that enum type. Handling all but one or two +enum constants is usually a coding mistake.

    + +
    + + +

    If there are only a handful of missing cases, add them to the end of the +switch statement. If there are many cases that do not need to be handled individually, +add a default case to handle them.

    + +

    If there are some enum constants that should never occur in this particular part of +the code, then program defensively by adding cases for those constants and explicitly throwing an +exception (rather than just having no cases for those constants).

    + +
    + + +

    In the following example, the case for 'YES' is missing. Therefore, if answer is +'YES', an exception is thrown at run time. To fix this, a case for 'YES' should be added.

    + + + +
    + + + +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • +
  • +Java Language Specification: +8.9 Enums, +14.11 The switch Statement. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Statements/MissingEnumInSwitch.ql b/java/ql/src/Likely Bugs/Statements/MissingEnumInSwitch.ql new file mode 100644 index 00000000000..dfea3ad72d9 --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/MissingEnumInSwitch.ql @@ -0,0 +1,22 @@ +/** + * @name Missing enum case in switch + * @description A 'switch' statement that is based on an 'enum' type and does not have cases for all + * the 'enum' constants is usually a coding mistake. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/missing-case-in-switch + * @tags reliability + * readability + * external/cwe/cwe-478 + */ + +import java + +from SwitchStmt switch, EnumType enum, EnumConstant missing +where + switch.getExpr().getType() = enum and + missing.getDeclaringType() = enum and + not exists(switch.getDefaultCase()) and + not switch.getAConstCase().getValue() = missing.getAnAccess() +select switch, "Switch statement does not have a case for $@.", missing, missing.getName() diff --git a/java/ql/src/Likely Bugs/Statements/PartiallyMaskedCatch.java b/java/ql/src/Likely Bugs/Statements/PartiallyMaskedCatch.java new file mode 100644 index 00000000000..4c62a6eb5c8 --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/PartiallyMaskedCatch.java @@ -0,0 +1,11 @@ +FileInputStream fis = null; +try { + fis = new FileInputStream(new File("may_not_exist.txt")); + // read from input stream +} catch (FileNotFoundException e) { + // ask the user and try again +} catch (IOException e) { + // more serious, abort +} finally { + if (fis!=null) { try { fis.close(); } catch (IOException e) { /*ignore*/ } } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Statements/PartiallyMaskedCatch.qhelp b/java/ql/src/Likely Bugs/Statements/PartiallyMaskedCatch.qhelp new file mode 100644 index 00000000000..9e882906e2d --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/PartiallyMaskedCatch.qhelp @@ -0,0 +1,68 @@ + + + + + +

    +An unreachable catch clause may indicate a logical mistake in the exception handling code +or may simply be unnecessary. +

    + +

    +Although certain unreachable catch clauses cause a compiler error, +there are also unreachable catch clauses that do not cause a compiler error. +A catch clause C is considered reachable by the compiler if both of the +following conditions are true:

    +
      +
    • A checked exception that is thrown in the try block is assignable + to the parameter of C.
    • +
    • There is no previous catch clause whose parameter type is + equal to, or a supertype of, the parameter type of C.
    • +
    +

    However, a catch clause that is considered reachable by the compiler can be +unreachable if both of the following conditions are true:

    +
      +
    • The catch clause's parameter type E does not include any unchecked exceptions.
    • +
    • All exceptions that are thrown in the try block whose type is a (strict) subtype of E + are already handled by previous catch clauses.
    • +
    + +
    + + +

    +Ensure that unreachable catch clauses are removed or that further corrections are made +to make them reachable. +

    +

    +Note that if a try-catch statement contains multiple catch +clauses, and an exception that is thrown in the try block matches more +than one of the catch clauses, only the first matching clause is executed. +

    + +
    + + +

    In the following example, the second catch clause is unreachable, and can be removed.

    + + + +
    + + + +
  • + The Java Language Specification: + Execution of try-catch, + Unreachable Statements. +
  • +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Statements/PartiallyMaskedCatch.ql b/java/ql/src/Likely Bugs/Statements/PartiallyMaskedCatch.ql new file mode 100644 index 00000000000..ca4bf2744d7 --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/PartiallyMaskedCatch.ql @@ -0,0 +1,110 @@ +/** + * @name Unreachable catch clause + * @description An unreachable 'catch' clause may indicate a mistake in exception handling or may + * be unnecessary. + * @kind problem + * @problem.severity warning + * @precision high + * @id java/unreachable-catch-clause + * @tags reliability + * correctness + * exceptions + * external/cwe/cwe-561 + */ + +import java + +/** + * Exceptions of type `rt` thrown from within statement `s` are caught by an inner try block + * and are therefore not propagated to the outer try block `t`. + */ +private +predicate caughtInside(TryStmt t, Stmt s, RefType rt) { + exists(TryStmt innerTry | innerTry.getParent+() = t.getBlock() | + s.getParent+() = innerTry.getBlock() and + caughtType(innerTry, _).hasSubtype*(rt) + ) +} + +/** + * Returns an exception type thrown from within the try block of `t` + * that is relevant to the catch clauses of `t` (i.e. not already + * caught by an inner try-catch). + */ +private +RefType getAThrownExceptionType(TryStmt t) { + exists(Method m, Exception e | + ( + m = t.getAResourceDecl().getAVariable().getType().(RefType).getAMethod() or + m = t.getAResourceExpr().getType().(RefType).getAMethod() + ) and + m.hasName("close") and + m.hasNoParameters() and + m.getAnException() = e and + result = e.getType() + ) or + exists(Call call, Exception e | + t.getBlock() = call.getEnclosingStmt().getParent*() or + t.getAResourceDecl() = call.getEnclosingStmt() + | + call.getCallee().getAnException() = e and + not caughtInside(t, call.getEnclosingStmt(), e.getType()) and + result = e.getType() + ) or + exists(ThrowStmt ts | + t.getBlock() = ts.getParent*() and + not caughtInside(t, ts, ts.getExpr().getType()) and + result = ts.getExpr().getType() + ) +} + +private +RefType caughtType(TryStmt try, int index) { + exists(CatchClause cc | cc = try.getCatchClause(index) | + if cc.isMultiCatch() + then result = cc.getVariable().getTypeAccess().(UnionTypeAccess).getAnAlternative().getType() + else result = cc.getVariable().getType() + ) +} + +private +predicate maybeUnchecked(RefType t) { + t.getASupertype*().hasQualifiedName("java.lang", "RuntimeException") or + t.getASupertype*().hasQualifiedName("java.lang", "Error") or + t.hasQualifiedName("java.lang", "Exception") or + t.hasQualifiedName("java.lang", "Throwable") +} + +predicate overlappingExceptions(RefType e1, RefType e2) { + exists(RefType throwable | throwable.hasQualifiedName("java.lang", "Throwable") | + throwable.hasSubtype*(e1) and + throwable.hasSubtype*(e2) and + e1.getASubtype*() = e2.getASubtype*() + ) +} + +from TryStmt try, int first, int second, RefType masking, RefType masked, string multiCatchMsg +where + masking = caughtType(try, first) and + masking.getASupertype+() = masked and + masked = caughtType(try, second) and + forall(RefType thrownType | + thrownType = getAThrownExceptionType(try) and + // If there's any overlap in the types, this catch block may be relevant. + overlappingExceptions(thrownType, masked) + | + exists(RefType priorCaughtType, int priorIdx | + priorIdx < second and + priorCaughtType = caughtType(try, priorIdx) and + thrownType.hasSupertype*(priorCaughtType) + ) + ) and + not maybeUnchecked(masked) and + if try.getCatchClause(second).isMultiCatch() + then multiCatchMsg = " for type " + masked.getName() + else multiCatchMsg = "" +select + try.getCatchClause(second), + "This catch-clause is unreachable" + multiCatchMsg + "; it is masked $@.", + try.getCatchClause(first), + "here for exceptions of type '" + masking.getName() + "'" diff --git a/java/ql/src/Likely Bugs/Statements/ReturnValueIgnored.java b/java/ql/src/Likely Bugs/Statements/ReturnValueIgnored.java new file mode 100644 index 00000000000..a435c0e3a89 --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/ReturnValueIgnored.java @@ -0,0 +1,3 @@ +FileSystem.get(conf); // Return value is not used + +FileSystem fs = FileSystem.get(conf); // Return value is assigned to 'fs' \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Statements/ReturnValueIgnored.qhelp b/java/ql/src/Likely Bugs/Statements/ReturnValueIgnored.qhelp new file mode 100644 index 00000000000..8e7034a0d4d --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/ReturnValueIgnored.qhelp @@ -0,0 +1,41 @@ + + + + + +

    If the result of a method call is used in most cases, +any calls to that method where the result is ignored are +inconsistent, and may be erroneous uses of the API. +Often, the result is some kind of status indicator, and is therefore important to check. +

    + +
    + + +

    +Ensure that the results of all calls to a particular +method are used. The return value of a method that returns a status value should normally be checked +before any modified data or allocated resource is used. +

    + +
    + + +

    Line 1 of the following example shows the value returned by get being ignored. Line +3 shows it being assigned to fs.

    + + + +
    + + + +
  • + CERT Secure Coding Standards: + EXP00-J. Do not ignore values returned by methods. +
  • + +
    +
    diff --git a/java/ql/src/Likely Bugs/Statements/ReturnValueIgnored.ql b/java/ql/src/Likely Bugs/Statements/ReturnValueIgnored.ql new file mode 100644 index 00000000000..90dd034cc9c --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/ReturnValueIgnored.ql @@ -0,0 +1,111 @@ +/** + * @name Method result ignored + * @description If most of the calls to a method use the return value + * of that method, the calls that do not check the return value may be mistakes. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/return-value-ignored + * @tags reliability + * correctness + * external/cwe/cwe-252 + * statistical + * non-attributable + */ +import java +import Chaining + +predicate checkedMethodCall(MethodAccess ma) { + relevantMethodCall(ma, _) and + not (ma.getParent() instanceof ExprStmt) +} + +/** + * Checks if a method is a used for setting up / verifying mocks. These are + * not usually "real" method calls, and so are not interesting for the purposes + * of this query. + */ +predicate isMockingMethod(Method m) { + isMustBeQualifierMockingMethod(m) or + isCardinalityClauseMethod(m) or + isStubberMethod(m) or + isReceiverClauseMethod(m) +} + +predicate isReceiverClauseMethod(Method m){ + m.getDeclaringType().getASupertype*().hasQualifiedName("org.jmock.syntax", "ReceiverClause") and + ( + m.hasName("of") + ) +} + +predicate isCardinalityClauseMethod(Method m){ + m.getDeclaringType().getASupertype*().hasQualifiedName("org.jmock.syntax", "CardinalityClause") and + ( + m.hasName("allowing") or + m.hasName("ignoring") or + m.hasName("never") or + m.hasName("exactly") or + m.hasName("atLeast") or + m.hasName("between") or + m.hasName("atMost") or + m.hasName("one") or + m.hasName("oneOf") + ) +} + +predicate isStubberMethod(Method m){ + m.getDeclaringType().getASupertype*().hasQualifiedName("org.mockito.stubbing", "Stubber") and + ( + m.hasName("when") or + m.hasName("doThrow") or + m.hasName("doAnswer") or + m.hasName("doNothing") or + m.hasName("doReturn") or + m.hasName("doCallRealMethod") + ) +} + +/** + * Some mocking methods must _always_ be used as a qualifier. + */ +predicate isMustBeQualifierMockingMethod(Method m){ + m.getDeclaringType().getASupertype*().hasQualifiedName("org.mockito", "Mockito") and + ( + m.hasName("verify") + ) +} + +predicate relevantMethodCall(MethodAccess ma, Method m) { + // For "return value ignored", all method calls are relevant. + ma.getMethod() = m and + not m.getReturnType().hasName("void") and + (not isMockingMethod(m) or isMustBeQualifierMockingMethod(m)) and + not isMockingMethod(ma.getQualifier().getProperExpr().(MethodAccess).getMethod()) +} + +predicate methodStats(Method m, int used, int total, int percentage) { + used = strictcount(MethodAccess ma | checkedMethodCall(ma) and m = ma.getMethod()) and + total = strictcount(MethodAccess ma | relevantMethodCall(ma, m)) and + percentage = used * 100 / total +} + +int chainedUses(Method m) { + result = count(MethodAccess ma, MethodAccess qual | + ma.getMethod() = m and + ma.getQualifier() = qual and + qual.getMethod() = m + ) +} + + +from MethodAccess unchecked, Method m, int percent, int total +where + relevantMethodCall(unchecked, m) and + not checkedMethodCall(unchecked) and + methodStats(m, _, total, percent) and + percent >= 90 and + not designedForChaining(m) and + chainedUses(m) * 100 / total <= 45 // no more than 45% of calls to this method are chained +select unchecked, "The result of the call is ignored, but " + percent.toString() + + "% of calls to " + m.getName() + " use the return value." diff --git a/java/ql/src/Likely Bugs/Statements/StaticFieldWrittenByInstance.java b/java/ql/src/Likely Bugs/Statements/StaticFieldWrittenByInstance.java new file mode 100644 index 00000000000..6e788026fe9 --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/StaticFieldWrittenByInstance.java @@ -0,0 +1,22 @@ +public class Customer { + private static List customers; + public void initialize() { + // AVOID: Static field is written to by instance method. + customers = new ArrayList(); + register(); + } + public static void add(Customer c) { + customers.add(c); + } +} + +// ... +public class Department { + public void addCustomer(String name) { + Customer c = new Customer(n); + // The following call overwrites the list of customers + // stored in 'Customer' (see above). + c.initialize(); + Customer.add(c); + } +} diff --git a/java/ql/src/Likely Bugs/Statements/StaticFieldWrittenByInstance.qhelp b/java/ql/src/Likely Bugs/Statements/StaticFieldWrittenByInstance.qhelp new file mode 100644 index 00000000000..2f3ebc48d03 --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/StaticFieldWrittenByInstance.qhelp @@ -0,0 +1,55 @@ + + + + + +

    A static field represents state shared between all instances of a particular +class. Typically, static methods are provided to manipulate this static state, and it +is bad practice to modify the static state of a class from an instance method +(or from a constructor).

    + +

    There are several reasons why this is bad practice. It can be very difficult to +keep the static state consistent when there are multiple instances through which it +could be modified. Such modifications represent a readability issue: +most programmers would expect a static method to affect static state, and an instance +method to affect instance state.

    + +
    + + +

    If the field should be an instance field, ensure that it does not have a static modifier.

    + +

    If the field does have to be static, evaluate the assumptions in the +code. Does the field really have to be modified directly in an instance method? It might be +better to access the field from within static methods, so that any concerns +about synchronization can be addressed without numerous synchronization statements in the code. Perhaps +the field modification is part of the static initialization of the class, and should be +moved to a static initializer or method.

    + +
    + + +

    In the following example, a static field, customers, is written to by an instance method, initialize. It is entirely +reasonable for another developer to assume that an instance method called +initialize should be called on each new instance, and that is +what the code in Department does. Unfortunately, the static field is +shared between all instances of Customer, and so each time initialize is called, +the old state is lost.

    + + + +

    The solution is to extract the static initialization of customers to a static method, where +it will happen exactly once.

    + +
    + + +
  • Java Language Specification: + 8.3.1.1 static Fields. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Statements/StaticFieldWrittenByInstance.ql b/java/ql/src/Likely Bugs/Statements/StaticFieldWrittenByInstance.ql new file mode 100644 index 00000000000..fd82ddb5597 --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/StaticFieldWrittenByInstance.ql @@ -0,0 +1,24 @@ +/** + * @name Static field written by instance method + * @description Writing to a static field from an instance method is prone to race conditions + * unless you use synchronization. In addition, it makes it difficult to keep the + * static state consistent and affects code readability. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/static-field-written-by-instance + * @tags reliability + * maintainability + */ +import java + +from FieldWrite fw, Field f, Callable c, string kind +where + fw.getField() = f and + f.isStatic() and + c = fw.getSite() and + not c.isStatic() and + f.getDeclaringType() = c.getDeclaringType() and + c.fromSource() and + if c instanceof Constructor then kind = "constructor for" else kind = "instance method" +select fw, "Write to static field " + f.getName() + " in " + kind + " " + c.getName() + "." diff --git a/java/ql/src/Likely Bugs/Statements/UseBraces.java b/java/ql/src/Likely Bugs/Statements/UseBraces.java new file mode 100644 index 00000000000..d1d9ab946c6 --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/UseBraces.java @@ -0,0 +1,13 @@ +class Cart { + Map items = ... + public void addItem(Item i) { + // No braces and misleading indentation. + if (i != null) + log("Adding item: " + i); + // Indentation suggests that the following statements + // are in the body of the 'if'. + Integer curQuantity = items.get(i.getID()); + if (curQuantity == null) curQuantity = 0; + items.put(i.getID(), curQuantity+1); + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Statements/UseBraces.qhelp b/java/ql/src/Likely Bugs/Statements/UseBraces.qhelp new file mode 100644 index 00000000000..16112a4fad2 --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/UseBraces.qhelp @@ -0,0 +1,68 @@ + + + + + +

    A control structure (an if statement or a loop) has a body that is either a block +of statements surrounded by curly braces or a single statement.

    + +

    If you omit braces, it is particularly important to ensure that the indentation of the code +matches the control flow of the code.

    + +
    + + +

    It is usually considered good practice to include braces for all control +structures in Java. This is because it makes it easier to maintain the code +later. For example, it's easy to see at a glance which part of the code is in the +scope of an if statement, and adding more statements to the body of the if +statement is less error-prone.

    + +

    You should also ensure that the indentation of the code is consistent with the actual flow of +control, so that it does not confuse programmers.

    + +
    + + +

    In the example below, the original version of Cart is missing braces. This means +that the code triggers a NullPointerException at runtime if i +is null.

    + + + +

    The corrected version of Cart does include braces, so +that the code executes as the indentation suggests.

    + + + +

    +In the following example the indentation may or may not be misleading depending on your tab width +settings. As such, mixing tabs and spaces in this way is not recommended, since what looks fine in +one context can be very misleading in another. +

    + + + +

    +If you mix tabs and spaces in this way, then you might get seemingly false positives, since your +tab width settings cannot be taken into account. +

    + +
    + + + +
  • + Java SE Documentation: + Compound Statements. +
  • +
  • + Wikipedia: + Indent style. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Statements/UseBraces.ql b/java/ql/src/Likely Bugs/Statements/UseBraces.ql new file mode 100644 index 00000000000..38289c516e5 --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/UseBraces.ql @@ -0,0 +1,128 @@ +/** + * @name Misleading indentation + * @description If a control structure does not use braces, misleading indentation makes it + * difficult to see which statements are within its scope. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id java/misleading-indentation + * @tags maintainability + * correctness + * logic + */ +import java + +/** + * A control structure for which the trailing body (the syntactically last part) + * is not a `Block`. This is either an `IfStmt` or a `LoopStmt`, but not a + * `DoStmt`, since do-while statements don't have a trailing body. + */ +predicate unbracedTrailingBody(Stmt ctrlStructure, Stmt trailingBody) { + not trailingBody instanceof Block and + ( + exists(IfStmt c | c = ctrlStructure | + trailingBody = c.getElse() and not trailingBody instanceof IfStmt or + trailingBody = c.getThen() and not exists(c.getElse()) + ) + or + exists(LoopStmt c | c = ctrlStructure | + not c instanceof DoStmt and trailingBody = c.getBody() + ) + ) +} + +/* + * The body of a `SwitchStmt` is a block, but it isn't represented explicitly + * in the AST as a `Block`, so we have to take it into account directly in the + * following two predicates. + */ + +/** + * Two consecutive statements in a `Block` statement or `SwitchStmt`. + */ +Stmt nextInBlock(Stmt s) { + exists(Block b, int i | + b.getStmt(i) = s and + b.getStmt(i+1) = result + ) + or + exists(SwitchStmt b, int i | + b.getStmt(i) = s and + b.getStmt(i+1) = result + ) +} + +/** The `Stmt.getParent()` relation restricted to not pass through `Block`s or `SwitchStmt`s. */ +Stmt nonBlockParent(Stmt s) { + result = s.getParent() and + not result instanceof Block and + not result instanceof SwitchStmt +} + +/** An else-if construction. */ +predicate ifElseIf(IfStmt s, IfStmt elseif) { + s.getElse() = elseif +} + +/** + * The statement `body` is an unbraced trailing body of a control structure and + * `succ` is the next statement in the surrounding `Block` (or `SwitchStmt`). + */ +predicate shouldOutdent(Stmt ctrl, Stmt body, Stmt succ, int bodycol, int succcol, int bodyline, int succline) { + unbracedTrailingBody(ctrl, body) and + succ = nextInBlock(nonBlockParent*(body)) and + bodycol = body.getLocation().getStartColumn() and + succcol = succ.getLocation().getStartColumn() and + bodyline = body.getLocation().getStartLine() and + succline = succ.getLocation().getStartLine() +} + +/** + * The statement `body` is an unbraced trailing body of a control structure and + * `succ` is the next statement in the surrounding `Block` (or `SwitchStmt`). + * The indentation of statement `succ` is suspect because it is indented + * the same way as `body` and thus visually suggests to be part of the same + * syntactic scope as `body`. + * + * Example situations: + * + * ``` + * if (cond) body; succ; + * + * if (cond) + * body; + * succ; + * ``` + */ +predicate suspectIndentation(Stmt ctrl, Stmt body, Stmt succ) { + exists(int bodycol, int succcol, int ctrlcol, int bodyline, int succline | + shouldOutdent(ctrl, body, succ, bodycol, succcol, bodyline, succline) and + (bodycol = succcol or bodyline = succline) and + // Disregard cases when `ctrl`, `body`, and `succ` are all equally indented. + (ctrlcol < bodycol or bodycol < succcol) and + ( + ctrlcol = ctrl.getLocation().getStartColumn() or + exists(IfStmt s | ifElseIf+(s, ctrl) and ctrlcol = s.getLocation().getStartColumn()) + ) + ) +} + +/** A statement that aborts the regular control-flow. */ +predicate abortsControlFlow(Stmt s) { + s instanceof JumpStmt or + s instanceof ReturnStmt or + s instanceof ThrowStmt +} + +from Stmt c, Stmt s, Stmt t +where + suspectIndentation(c, s, t) and + // Exclude control-flow-aborting statements as these cases are less likely to be logic errors. + not abortsControlFlow(s) and + // Exclude the double semicolon case `if (cond) s;;`. + not t instanceof EmptyStmt and + // `LocalClassDeclStmt`s yield false positives since their `Location` doesn't include the `class` keyword. + not t instanceof LocalClassDeclStmt +select + s, "Indentation suggests that $@ belongs to $@, but this is not the case; consider adding braces or adjusting indentation.", + t, "the next statement", c, "the control structure" diff --git a/java/ql/src/Likely Bugs/Statements/UseBraces2.java b/java/ql/src/Likely Bugs/Statements/UseBraces2.java new file mode 100644 index 00000000000..f411fb94b4b --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/UseBraces2.java @@ -0,0 +1,9 @@ +// Tab width 8 + if (b) // Indentation: 1 tab + f(); // Indentation: 2 tabs + g(); // Indentation: 8 spaces + +// Tab width 4 + if (b) // Indentation: 1 tab + f(); // Indentation: 2 tabs + g(); // Indentation: 8 spaces \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Statements/UseBracesGood.java b/java/ql/src/Likely Bugs/Statements/UseBracesGood.java new file mode 100644 index 00000000000..b896c6156d6 --- /dev/null +++ b/java/ql/src/Likely Bugs/Statements/UseBracesGood.java @@ -0,0 +1,12 @@ +class Cart { + Map items = ... + public void addItem(Item i) { + // Braces included. + if (i != null) { + log("Adding item: " + i); + Integer curQuantity = items.get(i.getID()); + if (curQuantity == null) curQuantity = 0; + items.put(i.getID(), curQuantity+1); + } + } +} \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Termination/ConstantLoopCondition.qhelp b/java/ql/src/Likely Bugs/Termination/ConstantLoopCondition.qhelp new file mode 100644 index 00000000000..20009f99688 --- /dev/null +++ b/java/ql/src/Likely Bugs/Termination/ConstantLoopCondition.qhelp @@ -0,0 +1,48 @@ + + + + +

    +Loops can contain multiple exit conditions, either directly in the loop +condition or as guards around break or return +statements. If none of the exit conditions can ever be satisfied, then +the loop will never terminate. +

    +
    + + +

    +When writing a loop that is intended to terminate, make sure that all the +necessary exit conditions can be satisfied and that loop termination is clear. +

    + +
    + + +

    +The following example searches for a field of a given name, and intends to +throw an exception if the field cannot be found. However, if the field cannot +be found, the double loop structure means that the exit conditions will never +be met, resulting in an infinite loop. +

    + + +

    +The solution is to rewrite the code as follows using an if-statement. +

    + + +
    + + + +
  • +Java Language Specification: +Blocks and Statements. +
  • + +
    + +
    diff --git a/java/ql/src/Likely Bugs/Termination/ConstantLoopCondition.ql b/java/ql/src/Likely Bugs/Termination/ConstantLoopCondition.ql new file mode 100644 index 00000000000..2dad736db5d --- /dev/null +++ b/java/ql/src/Likely Bugs/Termination/ConstantLoopCondition.ql @@ -0,0 +1,82 @@ +/** + * @name Constant loop condition + * @description A loop condition that remains constant throughout the iteration + * indicates faulty logic and is likely to cause infinite + * looping. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id java/constant-loop-condition + * @tags correctness + * external/cwe/cwe-835 + */ + +import java +import semmle.code.java.controlflow.Guards +import semmle.code.java.dataflow.SSA + +predicate loopWhileTrue(LoopStmt loop) { + loop instanceof ForStmt and not exists(loop.getCondition()) or + loop.getCondition().(BooleanLiteral).getBooleanValue() = true +} + +/** + * Holds if `exit` is a `return` or `break` statement that can exit the loop. + * + * Note that `throw` statements are not considered loop exits here, since a + * loop that appears to have a non-exceptional loop exit that cannot be reached + * is worth flagging even if it has a reachable exceptional loop exit. + */ +predicate loopExit(LoopStmt loop, Stmt exit) { + exit.getParent*() = loop.getBody() and + ( + exit instanceof ReturnStmt or + exit.(BreakStmt).(JumpStmt).getTarget() = loop.getParent*() + ) +} + +/** + * Holds if `cond` is a condition in the loop that guards all `return` and + * `break` statements that can exit the loop. + */ +predicate loopExitGuard(LoopStmt loop, Expr cond) { + exists(ConditionBlock cb, boolean branch | + cond = cb.getCondition() and + cond.getEnclosingStmt().getParent*() = loop.getBody() and + forex(Stmt exit | loopExit(loop, exit) | + cb.controls(exit.getBasicBlock(), branch) + ) + ) +} + +/** + * Holds if `loop.getCondition() = cond` and the loop can possibly execute more + * than once. That is, loops that are always terminated with a `return` or + * `break` are excluded as they are simply disguised `if`-statements. + */ +predicate mainLoopCondition(LoopStmt loop, Expr cond) { + loop.getCondition() = cond and + exists(Expr loopReentry, ControlFlowNode last | + if exists(loop.(ForStmt).getAnUpdate()) then + loopReentry = loop.(ForStmt).getUpdate(0) + else + loopReentry = cond + | + last.getEnclosingStmt().getParent*() = loop.getBody() and + last.getASuccessor().(Expr).getParent*() = loopReentry + ) +} + +from LoopStmt loop, Expr cond +where + (mainLoopCondition(loop, cond) or loopWhileTrue(loop) and loopExitGuard(loop, cond)) and + // None of the ssa variables in `cond` are updated inside the loop. + forex(SsaVariable ssa, RValue use | ssa.getAUse() = use and use.getParent*() = cond | + not ssa.getCFGNode().getEnclosingStmt().getParent*() = loop or + ssa.getCFGNode().(Expr).getParent*() = loop.(ForStmt).getAnInit() + ) and + // And `cond` does not use method calls, field reads, or array reads. + not exists(MethodAccess ma | ma.getParent*() = cond) and + not exists(FieldRead fa | fa.getParent*() = cond) and + not exists(ArrayAccess aa | aa.getParent*() = cond) +select loop, "Loop might not terminate, as this $@ is constant within the loop.", cond, "loop condition" diff --git a/java/ql/src/Likely Bugs/Termination/ConstantLoopConditionBad.java b/java/ql/src/Likely Bugs/Termination/ConstantLoopConditionBad.java new file mode 100644 index 00000000000..5edb24cc4c6 --- /dev/null +++ b/java/ql/src/Likely Bugs/Termination/ConstantLoopConditionBad.java @@ -0,0 +1,12 @@ +Object getField(Object obj, String name) throws NoSuchFieldError { + Class clazz = obj.getClass(); + while (clazz != null) { + for (Field f : clazz.getDeclaredFields()) { + if (f.getName().equals(name)) { + f.setAccessible(true); + return f.get(obj); + } + } + } + throw new NoSuchFieldError(name); +} diff --git a/java/ql/src/Likely Bugs/Termination/ConstantLoopConditionGood.java b/java/ql/src/Likely Bugs/Termination/ConstantLoopConditionGood.java new file mode 100644 index 00000000000..d900cd6adbf --- /dev/null +++ b/java/ql/src/Likely Bugs/Termination/ConstantLoopConditionGood.java @@ -0,0 +1,12 @@ +Object getField(Object obj, String name) throws NoSuchFieldError { + Class clazz = obj.getClass(); + if (clazz != null) { + for (Field f : clazz.getDeclaredFields()) { + if (f.getName().equals(name)) { + f.setAccessible(true); + return f.get(obj); + } + } + } + throw new NoSuchFieldError(name); +} diff --git a/java/ql/src/Likely Bugs/Termination/SpinOnField.java b/java/ql/src/Likely Bugs/Termination/SpinOnField.java new file mode 100644 index 00000000000..bb2174cc563 --- /dev/null +++ b/java/ql/src/Likely Bugs/Termination/SpinOnField.java @@ -0,0 +1,18 @@ +class Spin { + public boolean done = false; + + public void spin() { + while(!done){ + } + } +} + +class Spin { // optimized + public boolean done = false; + + public void spin() { + boolean cond = done; + while(!cond){ + } + } +} diff --git a/java/ql/src/Likely Bugs/Termination/SpinOnField.qhelp b/java/ql/src/Likely Bugs/Termination/SpinOnField.qhelp new file mode 100644 index 00000000000..3ac646c6910 --- /dev/null +++ b/java/ql/src/Likely Bugs/Termination/SpinOnField.qhelp @@ -0,0 +1,46 @@ + + + + + +

    Repeatedly reading a non-volatile field within the condition of an empty loop statement +may result in an infinite loop, +since a compiler optimization may move this field access out of the loop. +

    + +
    + + +

    In the following example, the method spin repeatedly +tests the field done in a loop. The method repeats the while-loop until the value +of the field done is set by another thread. +However, the compiler could optimize the code as shown in the second code snippet, because the field done +is not marked as volatile and there are no statements in the body of the loop that could change the value +of done. +The optimized version of spin loops forever, even when another thread would set done to true. +

    + + + +
    + + +

    Ensure that access to this field is properly synchronized. Alternatively, avoid spinning on the field and +instead use the wait and notifyAll methods or the java.util.concurrent +library to communicate between threads. +

    + +
    + + + +
  • + The Java Language Specification: + Threads and Locks. +
  • + + +
    +
    diff --git a/java/ql/src/Likely Bugs/Termination/SpinOnField.ql b/java/ql/src/Likely Bugs/Termination/SpinOnField.ql new file mode 100644 index 00000000000..bb9082b1f8c --- /dev/null +++ b/java/ql/src/Likely Bugs/Termination/SpinOnField.ql @@ -0,0 +1,68 @@ +/** + * @name Spin on field + * @description Repeatedly reading a non-volatile field within the condition of an empty loop may + * result in an infinite loop. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/spin-on-field + * @tags efficiency + * correctness + * concurrency + */ +import java + +/** A numerical comparison or an equality test. */ +class ComparisonOrEqTestExpr extends Expr { + ComparisonOrEqTestExpr() { + this instanceof ComparisonExpr or + this instanceof EqualityTest + } +} + +/** An empty statement or block. */ +class Empty extends Stmt { + Empty() { + this instanceof EmptyStmt or + this.(Block).getNumStmt() = 0 + } +} + +/** An empty loop statement. */ +class EmptyLoop extends Stmt { + EmptyLoop() { + exists(ForStmt stmt | stmt = this | + count(stmt.getAnInit()) = 0 and + count(stmt.getAnUpdate()) = 0 and + stmt.getStmt() instanceof Empty + ) or + this.(WhileStmt).getStmt() instanceof Empty or + this.(DoStmt).getStmt() instanceof Empty + } + + Expr getCondition() { + result = this.(ForStmt).getCondition() or + result = this.(WhileStmt).getCondition() or + result = this.(DoStmt).getCondition() + } +} + +/** An access to a field in this object. */ +class FieldAccessInThis extends VarAccess { + FieldAccessInThis() { + this.getVariable() instanceof Field and + (not this.hasQualifier() or this.getQualifier() instanceof ThisAccess) + } +} + +from EmptyLoop loop, FieldAccessInThis access, Field field, ComparisonOrEqTestExpr expr +where + loop.getCondition() = expr and + access.getParent() = expr and + field = access.getVariable() and + field.isStatic() and + not field.isFinal() and + not field.isVolatile() and + field.getType() instanceof RefType +select access, "Spinning on " + field.getName() + " in " + + loop.getEnclosingCallable().getName() + "." diff --git a/java/ql/src/META-INF/MANIFEST.MF b/java/ql/src/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..2e9435ab950 --- /dev/null +++ b/java/ql/src/META-INF/MANIFEST.MF @@ -0,0 +1,9 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Semmle Default Java Queries +Bundle-SymbolicName: com.semmle.plugin.semmlecode.queries;singleton:=true +Bundle-Version: 1.18.0.qualifier +Bundle-Vendor: Semmle Ltd. +Bundle-ActivationPolicy: lazy +Require-Bundle: com.semmle.plugin.qdt.ui;bundle-version="[1.18.0.qualifier,1.18.0.qualifier]" + diff --git a/java/ql/src/Metrics/Authors/AuthorsPerFile.qhelp b/java/ql/src/Metrics/Authors/AuthorsPerFile.qhelp new file mode 100644 index 00000000000..2775b643337 --- /dev/null +++ b/java/ql/src/Metrics/Authors/AuthorsPerFile.qhelp @@ -0,0 +1,39 @@ + + + +

    +A file's Javadoc comment can include a tag that lists the authors who have worked on the file. +

    + +

    +A file that has been changed by a large number of different authors is the product of many minds. +New authors working on the file may be less familiar with the design and implementation of the code than +the original authors, which can be a potential source of defects. Furthermore, +if the code is not carefully maintained, it often results in a lack of conceptual integrity. +

    + +
    + + +

    +There is clearly no way to reduce the number of authors that have worked +on a file - it is impossible to rewrite history. However, you should pay special attention in a code +review to a file that has been worked on by a large number of authors. The file may be need to be +refactored or rewritten by an individual, experienced programmer. +

    + + + +
    + + + +
  • +F. P. Brooks Jr, The Mythical Man-Month, Chapter 4. Addison-Wesley, 1974. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/Authors/AuthorsPerFile.ql b/java/ql/src/Metrics/Authors/AuthorsPerFile.ql new file mode 100644 index 00000000000..2579c16a53d --- /dev/null +++ b/java/ql/src/Metrics/Authors/AuthorsPerFile.ql @@ -0,0 +1,19 @@ +/** + * @name Number of authors (Javadoc) + * @description The number of different authors (by Javadoc tag) of a file. + * @kind treemap + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg max + * @id java/authors-per-file + * @tags maintainability + */ + +import java + +from CompilationUnit u, int num +where + num = strictcount(string s | + exists(Documentable d | d.getAuthor() = s and d.getCompilationUnit() = u) + ) +select u, num diff --git a/java/ql/src/Metrics/Callables/CCyclomaticComplexity.java b/java/ql/src/Metrics/Callables/CCyclomaticComplexity.java new file mode 100644 index 00000000000..73a92a42d8d --- /dev/null +++ b/java/ql/src/Metrics/Callables/CCyclomaticComplexity.java @@ -0,0 +1,15 @@ +int f(int i, int j) { + int result; + if(i % 2 == 0) { + result = i + j; + } + else { + if(j % 2 == 0) { + result = i * j; + } + else { + result = i - j; + } + } + return result; +} \ No newline at end of file diff --git a/java/ql/src/Metrics/Callables/CCyclomaticComplexity.qhelp b/java/ql/src/Metrics/Callables/CCyclomaticComplexity.qhelp new file mode 100644 index 00000000000..ea496155fbb --- /dev/null +++ b/java/ql/src/Metrics/Callables/CCyclomaticComplexity.qhelp @@ -0,0 +1,59 @@ + + + +

    +The cyclomatic complexity of a method (or constructor) is the number +of possible linearly-independent execution paths through that method (see [Wikipedia]). +It was originally introduced as a complexity measure by Thomas McCabe +[McCabe]. +

    + +

    A method with high cyclomatic complexity is typically difficult to +understand and test. +

    + +
    + + + + +

    +The control flow graph for this method is as follows: +

    + +Control Flow Diagram + +

    +As you can see from the graph, the number of linearly-independent execution +paths through the method is 3. Therefore, the cyclomatic complexity is 3.

    + +
    + + +

    +Simplify methods that have a high cyclomatic complexity. For example, +tidy up complex logic, and/or split methods into multiple smaller +methods using the 'Extract Method' refactoring from [Fowler]. +

    + + + +
    + + + +
  • +M. Fowler, Refactoring. Addison-Wesley, 1999. +
  • +
  • +T. J. McCabe, A Complexity Measure. IEEE Transactions on Software Engineering, SE-2(4), December 1976. +
  • +
  • +Wikipedia: Cyclomatic complexity. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/Callables/CCyclomaticComplexity.ql b/java/ql/src/Metrics/Callables/CCyclomaticComplexity.ql new file mode 100644 index 00000000000..eda6de3fc78 --- /dev/null +++ b/java/ql/src/Metrics/Callables/CCyclomaticComplexity.ql @@ -0,0 +1,18 @@ +/** + * @name Cyclomatic complexity of functions + * @description The number of possible execution paths through a method or constructor. + * @kind treemap + * @treemap.warnOn highValues + * @metricType callable + * @metricAggregate avg max sum + * @id java/cyclomatic-complexity-per-function + * @tags testability + * complexity + * maintainability + */ +import java + +from Callable c +where c.fromSource() +select c, c.getMetrics().getCyclomaticComplexity() as n +order by n desc diff --git a/java/ql/src/Metrics/Callables/CCyclomaticComplexity_ControlFlow.gv b/java/ql/src/Metrics/Callables/CCyclomaticComplexity_ControlFlow.gv new file mode 100644 index 00000000000..dfccd064080 --- /dev/null +++ b/java/ql/src/Metrics/Callables/CCyclomaticComplexity_ControlFlow.gv @@ -0,0 +1,11 @@ +digraph +{ + start -> iEven; + start -> iOdd; + iEven -> end; + iOdd -> jEven; + iOdd -> jOdd; + jEven -> end; + jOdd -> end; +} + diff --git a/java/ql/src/Metrics/Callables/CCyclomaticComplexity_ControlFlow.png b/java/ql/src/Metrics/Callables/CCyclomaticComplexity_ControlFlow.png new file mode 100644 index 0000000000000000000000000000000000000000..9aa9079fe4d9053c0bf40838e8e9d875ad183837 GIT binary patch literal 3010 zcmV;z3qACSP)|A3r}o-{0TU)6)O|0RR90YrC_X00001bW%=J06^y0W&i*S zOi4sRRCt{2oy~3>xe>?rIdVN1g}<^6-cg zp+NrDBHWHppnz7d?N|Z=1(k}L2m}i16g3eB6j3Q@A`B>^QPM;ZP*kCyi7=q#1h_yz zQ3D9p83YtpH!cuRA#!#04JalEP``n!AHSV@(TZecGQ0Xxl*kw`oJ~U#@1) zPxn+&o*fQTj02S2cqqFOV|HWe>}DPL&C9Zzx#l;w&(8aipMNMj4_bac#{9gn`T42y z^W^L7);j9Py0IE0_>5-*6|O&`!UJ+1$p{Uo3Lc8c69=;EXa{xnw1Eayw1YY$0Syfz zZ=xp+7@(G@#0}0m|4CGfk4N#zBWb5q70)^^j5)WE#K*4GetuqNAQ4@hcVU^hdN!A&- zv=BKz`dUX!;pkhwk`v$}v!Q8JpenZ=R+R#-zYz1FBS3&IC=n!aASfI_jSCbd5)5L% zfMSclqXXq+p(<{mHqn?s@nW!4h#)}7wT}vflq1#=0YF&e7!3$jgo4M!57a%u;0y28 zh*KZ@E}%*of?s#R$`uFd-!I@zrmZDdK9UXOSont4!Uu`CE9iY7Ps@xqY*#*>k$OPh ztBPE%=QPVmCQ#^-Lg%>G5d#YE0}7PwKgHNuv;~S)x%Yg?40NJLpmM^g?X@7)Sp$S$gS|b5doSz^h?6NS4Ul=AZ^KjPzDW#KZ?*o5`gi^9>ipqD z256<``Tztnj^66`FUzCXx_jvLo_v%>R0CNpL4~fVZ@XT`H*;MsbyplaET#bp>9nMu z*3@)+v-#LNkp?QC)j-8&)HR4(){Yo!)AP-mf89SAq$N5c*7GS#*reesX;=KmIu_byXi z8|s3ql7RF8Xd}n}Iaz-m0(tu()!k(h7IFYx>h+>&M`57K%-8lq7bhE*=EIc=1Tu4| z|EhWU<9;1xw^Dz4X#x44Woq8`)CaHkRQn+V)OkJ*b-8&894!wmcc%AgUr)lDXW8x9 z(O=t-Eh`x)yYWzdBgX8;)Y;8C@|%}sH*?KzZl9m`BRl_4ejc>^e2n>dVe|7->+C%F zwMeaXFqeg*t3iaatkodi@=J8Rj3kAbjc203iXx-Ho>4LYvT}&rW&Yv;kqAHAB@oL* zp;c*ed3FYliFOLaG0|>;7$({+5W_^0Kn{Ck2Sl&49Rtx#Bnjk7Pc~5qh+3HQ0{F9z zp@;xc3)wjk)kK0oGVxx&Lm*stdUFo4tc4 zOBxV45Up+epQh!%@c^k28bB#4z_%hy~Ww>je7QFMWy3FP0I5}q%{ zw?MFfLfs(ioN*!sn^O1qDVD}+4y6KJzgg(}R|Jc$NO$9=O-8nU_I6&wCiL=R}VgTWz{$Mcy zUGY~W(93nC#5ezr1q9`($Ux>nBoM9g9o=8*FBi-G`fDA8Rz4F03J0QjEqi}io*n*r zHrMS~rT643kax%QhN-8V=N1s1C7Of2OygUd)ySWCo&HKwi44=-QBK zv(SMm=hs0XnhoT;sH*7hGPZ?+K-)mHAdp!`(e*=h5Ze8X2?X`j-d3$v{XjGe$onj- zims=wgV0usD`RR20(n}s6^Legqr6Z1&DZu>)&!v)y?Qa<7D)%{#Q=uHKOPE$ub;q>QGt+zAW*O@ z6bNz>jR=H!(NG|Povv82@qk3Od|_HKAkdDZ0f}rI1Pg?H(1?r$B(wDtREq$?o##kE zLfee)ubt1uRjz4GA%Vpf;{kUO4*TVX69d$r01#H}zM zP}ScU;HWXafmlGgw}Zq5#5WKV$XsNxfr=yp@qntpaz&+*48#NK-{>|F_qBd}peh8E z^gB;XAge3vFL+G>WQEf8fS{kulLoS*AFUcoGZ72Oy3GbaDJJ3pJ+5bNUv8<5;{+Zq zP`fH^+Nu4X{)38Kpn3(`Qe7V=nkNOwv2x!;*QMbk0J&dQomO47aDiA`auDGHab3hw z;okPX6TI1>A2WCcM@2UJtkKvtO8 zgch2&K+{^tXtAgJtELV0!^B4Jsm&+V!N;S+8mJ#8HsQndcJ@o_aO{PNjSZCDcqqFO zV|HWe{AL~5&C9Zzx#l;w&(8aipMNMn4_bacM&3^b0>bxG4};gZzYx|FKG47`E#Rcq zX+Vy(j&w-^8m + + +

    +A method that contains a high number of lines of code has a number of problems:

    + +
      +
    • It can be difficult to understand, difficult to check, and a common source of defects +(particularly towards the end of the method, because few people read that far).
    • +
    • It is likely to lack cohesion because it has too many responsibilities.
    • +
    • It increases the risk of introducing new defects during routine code changes.
    • +
    + +
    + + +

    +Break up long methods into smaller methods by extracting +parts of their functionality into simpler methods, for example by using the 'Extract +Method' refactoring from [Fowler]. As an approximate guide, a method should fit on one screen or +side of Letter/A4 paper. +

    + + + +
    + + + +
  • +M. Fowler, Refactoring, pp. 89-95. Addison-Wesley, 1999. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/Callables/CLinesOfCode.ql b/java/ql/src/Metrics/Callables/CLinesOfCode.ql new file mode 100644 index 00000000000..3d8a8fa23a3 --- /dev/null +++ b/java/ql/src/Metrics/Callables/CLinesOfCode.ql @@ -0,0 +1,17 @@ +/** + * @name Lines of code in methods + * @description The number of lines of code in a method. + * @kind treemap + * @treemap.warnOn highValues + * @metricType callable + * @metricAggregate avg sum max + * @id java/lines-of-code-per-function + * @tags maintainability + * complexity + */ +import java + +from Callable c +where c.fromSource() +select c, c.getMetrics().getNumberOfLinesOfCode() as n +order by n desc diff --git a/java/ql/src/Metrics/Callables/CLinesOfComment.qhelp b/java/ql/src/Metrics/Callables/CLinesOfComment.qhelp new file mode 100644 index 00000000000..be4caa510e0 --- /dev/null +++ b/java/ql/src/Metrics/Callables/CLinesOfComment.qhelp @@ -0,0 +1,71 @@ + + + +

    +This metric measures the (absolute) number of comment lines for each method. +

    + +

    +Whilst the absolute number of comment lines a method has is not always +especially meaningful when taken out of context, methods with very few +comment lines are almost undocumented and may be hard to understand. +

    + +
    + + +

    +Methods containing very few comments should be examined to see whether or not +they need more documentation. At a minimum, most methods (aside from getters, +setters and other trivial methods) usually benefit from at least a brief +comment as to their purpose. Less trivial methods can often be improved by +adding appropriate comments to communicate the programmer's intent, or to +summarize what a bit of code does. (See [McConnell] for more on writing good +comments.) +

    + +

    +There are some cases, however, where adding more comments should not be the +main focus of refactoring: +

    + +
      +
    • +If your method is very long, the priority is to break it up into smaller +methods with sensible names that communicate intent. These new methods +should then be commented appropriately. +
    • + +
    • +If your method is very hard to understand, you should redesign the way +it works to make things simpler. Tricky code is bad code - you shouldn't +document it, you should rewrite it (and then document it). +
    • + +
    • +If your method is already absolutely clear and you don't have anything useful +to say, don't add meaningless comments. Comments are there to communicate +information that is not obvious from the code, not to repeat what's already +there. If you can't say something that couldn't be generated by an automatic +documentation generator (see [Atwood]), don't say anything. +
    • +
    + + + +
    + + + +
  • +J. Atwood. Avoiding Undocumentation. Published online, 2005. +
  • +
  • +S. McConnell. Code Complete, 2nd Edition. Microsoft Press, 2004. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/Callables/CLinesOfComment.ql b/java/ql/src/Metrics/Callables/CLinesOfComment.ql new file mode 100644 index 00000000000..637baa56795 --- /dev/null +++ b/java/ql/src/Metrics/Callables/CLinesOfComment.ql @@ -0,0 +1,17 @@ +/** + * @name Lines of comment in methods + * @description The number of comment lines in a method. + * @kind treemap + * @treemap.warnOn lowValues + * @metricType callable + * @metricAggregate avg sum max + * @id java/lines-of-comment-per-function + * @tags maintainability + * documentation + */ +import java + +from Callable c +where c.fromSource() +select c, c.getMetrics().getNumberOfCommentLines() as n +order by n desc diff --git a/java/ql/src/Metrics/Callables/CNumberOfCalls.qhelp b/java/ql/src/Metrics/Callables/CNumberOfCalls.qhelp new file mode 100644 index 00000000000..41093ce919e --- /dev/null +++ b/java/ql/src/Metrics/Callables/CNumberOfCalls.qhelp @@ -0,0 +1,72 @@ + + + +

    If the number of calls that is made by a method (or constructor) to other methods is high, +the method can be difficult to +understand, because you have to read through all the methods that it calls +to fully understand what it does. There are various reasons why +a method may make a high number of calls, including: +

    + +
      +
    • +The method is simply too large in general. +
    • + +
    • +The method has too many responsibilities (see [Martin]). +
    • + +
    • +The method spends all of its time delegating rather than doing any work itself. +
    • +
    + +
    + + +

    +The appropriate action depends on the reason why the method +makes a high number of calls: +

    + +
      +
    • +If the method is too large, you should refactor it into multiple smaller +methods, using the 'Extract Method' refactoring from [Fowler], for example. +
    • + +
    • +If the method is taking on too many responsibilities, a new layer of methods +can be introduced below the top-level method, each of which can do some of the +original work. The top-level method then only needs to delegate to a much +smaller number of methods, which themselves delegate to the methods lower down. +
    • + +
    • +If the method spends all of its time delegating, some of the work that is done by the +subsidiary methods can be moved into the top-level method, and the subsidiary +methods can be removed. This is the refactoring called 'Inline +Method' in [Fowler]. +
    • +
    + + + +
    + + + +
  • +M. Fowler, Refactoring. Addison-Wesley, 1999. +
  • +
  • +R. Martin. Agile Software Development: Principles, Patterns, and Practices +Chapter 8 - SRP: The Single-Responsibility Principle. Pearson Education, 2003. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/Callables/CNumberOfCalls.ql b/java/ql/src/Metrics/Callables/CNumberOfCalls.ql new file mode 100644 index 00000000000..a5b3565445c --- /dev/null +++ b/java/ql/src/Metrics/Callables/CNumberOfCalls.ql @@ -0,0 +1,19 @@ +/** + * @name Number of calls in methods + * @description The number of calls that is made by a method or constructor. + * @kind treemap + * @treemap.warnOn highValues + * @metricType callable + * @metricAggregate avg sum max + * @id java/calls-per-function + * @tags testability + * complexity + * maintainability + */ +import java + +from Callable c, int n +where c.fromSource() and + n = count(Call call | call.getEnclosingCallable() = c) +select c, n +order by n desc diff --git a/java/ql/src/Metrics/Callables/CNumberOfParameters.qhelp b/java/ql/src/Metrics/Callables/CNumberOfParameters.qhelp new file mode 100644 index 00000000000..ecd488d488e --- /dev/null +++ b/java/ql/src/Metrics/Callables/CNumberOfParameters.qhelp @@ -0,0 +1,89 @@ + + + +

    +A method (or constructor) that uses a high number of formal parameters makes maintenance more difficult: +

    + +
      +
    • It is difficult to write a call to the method, because the programmer must know how to +supply an appropriate value for each parameter.
    • + +
    • It is externally difficult to understand, because calls +to the method are longer than a single line of code.
    • + +
    • It can be internally difficult to understand, because it +has so many dependencies.
    • +
    + +
    + + +

    +Restrict the number of formal parameters for a method, according to the reason for the high number: +

    + +
      +
    • Several of the parameters are logically related, but are +passed into the method separately. The parameters that are logically related should be grouped together +(see the 'Introduce Parameter Object' refactoring on pp. 238-242 of [Fowler]).
    • + +
    • The method has too many responsibilities. It should be broken into multiple methods (see the +'Extract Method' refactoring on pp. 89-95 of [Fowler]), and each new method should be passed +a subset of the original parameters.
    • + +
    • The method has redundant parameters that are not used. The two main reasons for this are: +(1) parameters were added for future extensibility but are never used; (2) the body of the method was changed +so that it no longer uses certain parameters, but the method signature was not +correspondingly updated. In both cases, the theoretically correct solution is to delete the unused +parameters (see the 'Remove Parameter' refactoring on pp. 223-225 of [Fowler]), although you must do +this cautiously if the method is part of a published interface.
    • +
    + +

    When a method is part of a published interface, one possible solution is to add a new, wrapper +method to the interface that has a tidier signature. Alternatively, you can publish a new version of +the interface that has a better design. Clearly, however, neither of these solutions is ideal, +so you should take care to design interfaces the right way from the start.

    + +

    The practice of adding parameters for future extensibility is especially +bad. It is confusing to other programmers, who are uncertain what values they should pass +in for these unnecessary parameters, and it adds unused code that is potentially difficult to remove +later.

    + +
    +
    + +

    In the following example, although the parameters are logically related, they are passed into the +printAnnotation method separately.

    + + + +

    In the following modified example, the parameters that are logically related are grouped together +in a class, and an instance of the class is passed into the method instead.

    + + + +

    In the following example, the printMembership method has too many responsibilities, +and so needs to be passed four arguments.

    + + + +

    In the following modified example, printMembership has been broken into four methods. +(For brevity, only one method is shown.) As a result, each new method needs to be passed only one +of the original four arguments.

    + + + +
    + + + +
  • +M. Fowler, Refactoring. Addison-Wesley, 1999. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/Callables/CNumberOfParameters.ql b/java/ql/src/Metrics/Callables/CNumberOfParameters.ql new file mode 100644 index 00000000000..f2db1f9b752 --- /dev/null +++ b/java/ql/src/Metrics/Callables/CNumberOfParameters.ql @@ -0,0 +1,18 @@ +/** + * @name Number of parameters to methods + * @description The number of parameters of a method or constructor. + * @kind treemap + * @treemap.warnOn highValues + * @metricType callable + * @metricAggregate avg max + * @id java/parameters-per-function + * @tags testability + * complexity + * maintainability + */ +import java + +from Callable c +where c.fromSource() +select c, c.getMetrics().getNumberOfParameters() as n +order by n desc diff --git a/java/ql/src/Metrics/Callables/CNumberOfParameters1.java b/java/ql/src/Metrics/Callables/CNumberOfParameters1.java new file mode 100644 index 00000000000..9953b5efa07 --- /dev/null +++ b/java/ql/src/Metrics/Callables/CNumberOfParameters1.java @@ -0,0 +1,6 @@ +void printAnnotation(String annotationMessage, int annotationLine, int annotationOffset, + int annotationLength) { + System.out.println("Message: " + annotationMessage); + System.out.println("Line: " + annotationLine); + System.out.println("Offset: " + annotationOffset); + System.out.println("Length: " + annotationLength); \ No newline at end of file diff --git a/java/ql/src/Metrics/Callables/CNumberOfParameters1Good.java b/java/ql/src/Metrics/Callables/CNumberOfParameters1Good.java new file mode 100644 index 00000000000..acf3bf81abb --- /dev/null +++ b/java/ql/src/Metrics/Callables/CNumberOfParameters1Good.java @@ -0,0 +1,10 @@ +class Annotation { + //... +} + +void printAnnotation(Annotation annotation) { + System.out.println("Message: " + annotation.getMessage()); + System.out.println("Line: " + annotation.getLine()); + System.out.println("Offset: " + annotation.getOffset()); + System.out.println("Length: " + annotation.getLength()); +} \ No newline at end of file diff --git a/java/ql/src/Metrics/Callables/CNumberOfParameters2.java b/java/ql/src/Metrics/Callables/CNumberOfParameters2.java new file mode 100644 index 00000000000..607b4240ddf --- /dev/null +++ b/java/ql/src/Metrics/Callables/CNumberOfParameters2.java @@ -0,0 +1,20 @@ +void printMembership(Set fellows, Set members, + Set associates, Set students) { + for(Fellow f: fellows) { + System.out.println(f); + } + for(Member m: members) { + System.out.println(m); + } + for(Associate a: associates) { + System.out.println(a); + } + for(Student s: students) { + System.out.println(s); + } +} + +void printRecords() { + //... + printMembership(fellows, members, associates, students); +} \ No newline at end of file diff --git a/java/ql/src/Metrics/Callables/CNumberOfParameters2Good.java b/java/ql/src/Metrics/Callables/CNumberOfParameters2Good.java new file mode 100644 index 00000000000..925c54492d8 --- /dev/null +++ b/java/ql/src/Metrics/Callables/CNumberOfParameters2Good.java @@ -0,0 +1,15 @@ +void printFellows(Set fellows) { + for(Fellow f: fellows) { + System.out.println(f); + } +} + +//... + +void printRecords() { + //... + printFellows(fellows); + printMembers(members); + printAssociates(associates); + printStudents(students); +} \ No newline at end of file diff --git a/java/ql/src/Metrics/Callables/CNumberOfStatements.qhelp b/java/ql/src/Metrics/Callables/CNumberOfStatements.qhelp new file mode 100644 index 00000000000..948059a1345 --- /dev/null +++ b/java/ql/src/Metrics/Callables/CNumberOfStatements.qhelp @@ -0,0 +1,42 @@ + + + +

    +This metric measures the number of statements for each method. +

    + +

    +Methods that consist of too many statements are hard to understand, +difficult to check and a common source of bugs (particularly towards +the end of the method, since few people ever read that far). They often +lack cohesion because they are trying to do too many things at once, and +should be refactored into multiple, smaller methods. As a rough guide, +methods should be able to fit on a single screen or side of A4. Anything +longer than that increases the risk of introducing new defects during routine code changes. +

    + +
    + + +

    +Over-long methods should be broken up into smaller ones by extracting +parts of their functionality out into auxiliary methods, using the +technique that Martin Fowler's Refactoring book calls 'Extract +Method' (see References). +

    + + + +
    + + + +
  • +M. Fowler. Refactoring pp. 89-95. Addison-Wesley, 1999. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/Callables/CNumberOfStatements.ql b/java/ql/src/Metrics/Callables/CNumberOfStatements.ql new file mode 100644 index 00000000000..8dbe28af52b --- /dev/null +++ b/java/ql/src/Metrics/Callables/CNumberOfStatements.ql @@ -0,0 +1,17 @@ +/** + * @name Number of statements in methods + * @description The number of statements in a method or constructor. + * @kind treemap + * @treemap.warnOn highValues + * @metricType callable + * @metricAggregate avg sum max + * @id java/statements-per-function + * @tags maintainability + */ +import java + +from Callable c, int n +where c.fromSource() and + n = count(Stmt s | s.getEnclosingCallable() = c) +select c, n +order by n desc diff --git a/java/ql/src/Metrics/Callables/StatementNestingDepth.java b/java/ql/src/Metrics/Callables/StatementNestingDepth.java new file mode 100644 index 00000000000..e74e37b348e --- /dev/null +++ b/java/ql/src/Metrics/Callables/StatementNestingDepth.java @@ -0,0 +1,11 @@ +public static void printCharacterCodes_Bad(String[] strings) { + if (strings != null) { + for (String s : strings) { + if (s != null) { + for (int i = 0; i < s.length(); i++) { + System.out.println(s.charAt(i) + "=" + (int) s.charAt(i)); + } + } + } + } +} \ No newline at end of file diff --git a/java/ql/src/Metrics/Callables/StatementNestingDepth.qhelp b/java/ql/src/Metrics/Callables/StatementNestingDepth.qhelp new file mode 100644 index 00000000000..b4d5f238155 --- /dev/null +++ b/java/ql/src/Metrics/Callables/StatementNestingDepth.qhelp @@ -0,0 +1,56 @@ + + + +

    +A method that contains a high level of nesting can be very difficult to understand. As noted in +[McConnell], the human brain cannot easily handle more than three levels of nested if +statements.

    + +
    + + +

    +Extract nested statements into new methods, for example by using the 'Extract Method' refactoring +from [Fowler].

    + +

    +For more ways to reduce the level of nesting in a method, see [McConnell]. +

    + +

    +Furthermore, a method that has a high level of nesting often indicates that its design can be +improved in other ways, as well as dealing with the nesting problem itself. +

    + +
    + + +

    +In the following example, the code has four levels of nesting and is unnecessarily difficult to read. +

    + + + +

    +In the following modified example, some of the nested statements have been extracted into a new method +PrintAllCharInts. +

    + + + +
    + + + +
  • +M. Fowler, Refactoring, pp. 89-95. Addison-Wesley, 1999. +
  • +
  • +S. McConnell, Code Complete, 2nd Edition, §19.4. Microsoft Press, 2004. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/Callables/StatementNestingDepth.ql b/java/ql/src/Metrics/Callables/StatementNestingDepth.ql new file mode 100644 index 00000000000..c2def7a1dc7 --- /dev/null +++ b/java/ql/src/Metrics/Callables/StatementNestingDepth.ql @@ -0,0 +1,42 @@ +/** + * @name Statement nesting depth + * @description The maximum level of nesting of statements (for example 'if', 'for', 'while') in a + * method. Blocks are not counted. + * @kind treemap + * @treemap.warnOn highValues + * @metricType callable + * @metricAggregate avg max + * @id java/statement-nesting-depth-per-function + * @tags maintainability + * complexity + */ +import java + +/** + * The parent of a statement, excluding some common cases that don't really make + * sense for nesting depth. For example, in `if (...) { } else if (...) { }` we don't + * consider the second `if` nested. Blocks are also skipped. + */ +predicate realParent(Stmt inner, Stmt outer) { + if skipParent(inner) then + realParent(inner.getParent(), outer) + else + outer = inner.getParent() +} + +predicate skipParent(Stmt s) { + exists(Stmt parent | parent = s.getParent() | + (s instanceof IfStmt and parent.(IfStmt).getElse() = s) + or + parent instanceof Block + ) +} + +predicate nestingDepth(Stmt s, int depth) { + depth = count(Stmt enclosing | realParent+(s, enclosing)) +} + +from Method m, int depth +where depth = max(Stmt s, int aDepth | s.getEnclosingCallable() = m and nestingDepth(s, aDepth) | aDepth) +select m, depth +order by depth diff --git a/java/ql/src/Metrics/Callables/StatementNestingDepthGood.java b/java/ql/src/Metrics/Callables/StatementNestingDepthGood.java new file mode 100644 index 00000000000..b42b9712da5 --- /dev/null +++ b/java/ql/src/Metrics/Callables/StatementNestingDepthGood.java @@ -0,0 +1,14 @@ +public static void printAllCharInts(String s) { + if (s != null) { + for (int i = 0; i < s.length(); i++) { + System.out.println(s.charAt(i) + "=" + (int) s.charAt(i)); + } + } +} +public static void printCharacterCodes_Good(String[] strings) { + if (strings != null) { + for(String s : strings){ + printAllCharInts(s); + } + } +} \ No newline at end of file diff --git a/java/ql/src/Metrics/Dependencies/ExternalDependencies.ql b/java/ql/src/Metrics/Dependencies/ExternalDependencies.ql new file mode 100644 index 00000000000..70dd46e4752 --- /dev/null +++ b/java/ql/src/Metrics/Dependencies/ExternalDependencies.ql @@ -0,0 +1,34 @@ +/** + * @name External dependencies + * @description Count the number of dependencies a Java source file has on jar files. + * @kind treemap + * @treemap.warnOn highValues + * @metricType externalDependency + * @precision medium + * @id java/external-dependencies + */ + +import java +import semmle.code.java.DependencyCounts + +/* + * These two columns encode four logical columns: + * + * 1. Java source file where the dependency originates + * 2. Name of the JAR file containing the target of the dependency + * 3. Best guess at a version based on a dashed suffix of the JAR file + * 4. Number of dependencies from the Java file to the jar + * + * Ideally this query would therefore return four columns, + * but this would require changing the dashboard database schema + * and dashboard extractor. + * + * The first column (the Java source file) is prepended with a '/' + * so that the file path matches the path used for the file in the + * dashboard database, which is implicitly relative to the source + * archive location. + */ +from File sourceFile, int total, string entity +where fileJarDependencyCount(sourceFile, total, entity) +select entity, total +order by total desc diff --git a/java/ql/src/Metrics/Dependencies/ExternalDependenciesSourceLinks.ql b/java/ql/src/Metrics/Dependencies/ExternalDependenciesSourceLinks.ql new file mode 100644 index 00000000000..e710e4b3e19 --- /dev/null +++ b/java/ql/src/Metrics/Dependencies/ExternalDependenciesSourceLinks.ql @@ -0,0 +1,21 @@ +/** + * @name External dependency source links + * @kind source-link + * @metricType externalDependency + * @id java/dependency-source-links + */ + +import java +import semmle.code.java.DependencyCounts + +/* + * This query creates the source links for the ExternalDependencies.ql query. + * Although the entities in question are of the form '/file/path<|>dependency<|>version', + * the /file/path is a bare string relative to the root of the source archive, and not + * tied to a particular revision. We need the File entity (the second column here) to + * recover that information once we are in the dashboard database, using the + * ExternalEntity.getASourceLink() method. + */ +from File sourceFile, string entity +where fileJarDependencyCount(sourceFile, _, entity) +select entity, sourceFile diff --git a/java/ql/src/Metrics/Files/DuplicationProblems.qhelp b/java/ql/src/Metrics/Files/DuplicationProblems.qhelp new file mode 100644 index 00000000000..e55f8f8e455 --- /dev/null +++ b/java/ql/src/Metrics/Files/DuplicationProblems.qhelp @@ -0,0 +1,17 @@ + + + +

    +Duplicated code increases overall code size, making the code base +harder to maintain and harder to understand. It also becomes harder to fix bugs, +since a programmer applying a fix to one copy has to always remember to update +other copies accordingly. Finally, code duplication is generally an indication of +a poorly designed or hastily written code base, which typically suffers from other +problems as well. +

    + + +
    +
    diff --git a/java/ql/src/Metrics/Files/FAfferentCoupling.qhelp b/java/ql/src/Metrics/Files/FAfferentCoupling.qhelp new file mode 100644 index 00000000000..19318280130 --- /dev/null +++ b/java/ql/src/Metrics/Files/FAfferentCoupling.qhelp @@ -0,0 +1,58 @@ + + + +

    +This metric measures the number of incoming dependencies for each compilation +unit, i.e. the number of other compilation units that depend on it. +(A compilation unit is a .java or .class file.) +

    + +

    +Compilation units that are depended on by many other units are typically +time-consuming to change, because changing them forces all of the units that +depend on them to be recompiled. Most systems have some units like this at the +lowest level, and it is not necessarily a problem as long as the unit is only +changed infrequently -- for instance, a compilation unit containing a string +class might be depended upon by everything, but would be unlikely to change on +a regular basis. +

    + +

    +What is problematic is if a compilation unit that is heavily depended upon +also depends on a lot of other compilation units that have reasons to +change frequently, because it is then likely to need recompiling on a regular +basis, triggering a ripple effect of recompilation throughout the code: such +units are known as hubs, and can be responsible for greatly increasing the +time needed for incremental recompilation. +

    + +
    + + +

    +Aside from the advice given for classes that have high afferent coupling, +which still applies when a compilation unit contains a single class, it +is important to look at the number of classes that are in a particular +compilation unit when dealing with high afferent coupling at the unit +level. Having multiple classes in the same file can cause far more units +to depend on a particular unit than would otherwise be the case: the +solution is to move each class to its own compilation unit. This is a good +idea in any case, as it makes the code more understandable and easier to +maintain. +

    + + + +
    + + + +
  • +M. Fowler. Refactoring. Addison-Wesley, 1999. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/Files/FAfferentCoupling.ql b/java/ql/src/Metrics/Files/FAfferentCoupling.ql new file mode 100644 index 00000000000..b344153476f --- /dev/null +++ b/java/ql/src/Metrics/Files/FAfferentCoupling.ql @@ -0,0 +1,22 @@ +/** + * @name Incoming file dependencies + * @description The number of compilation units that depend on a compilation unit. + * @kind treemap + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg max + * @id java/incoming-file-dependencies + * @tags changeability + * modularity + */ +import java + +from CompilationUnit f, int n +where + n = count(File g | + exists(Class c | c.fromSource() and c.getCompilationUnit() = f | + exists(Class d | d.fromSource() and d.getCompilationUnit() = g | depends(d,c)) + ) + ) +select f, n +order by n desc diff --git a/java/ql/src/Metrics/Files/FCommentRatio.qhelp b/java/ql/src/Metrics/Files/FCommentRatio.qhelp new file mode 100644 index 00000000000..8c24e275296 --- /dev/null +++ b/java/ql/src/Metrics/Files/FCommentRatio.qhelp @@ -0,0 +1,6 @@ + + + + diff --git a/java/ql/src/Metrics/Files/FCommentRatio.ql b/java/ql/src/Metrics/Files/FCommentRatio.ql new file mode 100644 index 00000000000..87791704b13 --- /dev/null +++ b/java/ql/src/Metrics/Files/FCommentRatio.ql @@ -0,0 +1,20 @@ +/** + * @name Percentage of documentation in files + * @description The percentage of comment lines in a file. + * @kind treemap + * @treemap.warnOn lowValues + * @metricType file + * @metricAggregate avg max + * @id java/comment-ratio-per-file + * @tags maintainability + * documentation + */ +import java + +from CompilationUnit f, float comments, float loc, float ratio +where f.getTotalNumberOfLines() > 0 + and comments = f.getNumberOfCommentLines() + and loc = f.getTotalNumberOfLines() + and ratio = 100.0 * comments / loc +select f, ratio +order by ratio desc \ No newline at end of file diff --git a/java/ql/src/Metrics/Files/FCyclomaticComplexity.java b/java/ql/src/Metrics/Files/FCyclomaticComplexity.java new file mode 100644 index 00000000000..5a09f4d78c6 --- /dev/null +++ b/java/ql/src/Metrics/Files/FCyclomaticComplexity.java @@ -0,0 +1,21 @@ +int f(int i, int j) { + // start + int result; + if(i % 2 == 0) { + // iEven + result = i + j; + } + else { + // iOdd + if(j % 2 == 0) { + // jEven + result = i * j; + } + else { + // jOdd + result = i - j; + } + } + return result; + // end +} \ No newline at end of file diff --git a/java/ql/src/Metrics/Files/FCyclomaticComplexity.qhelp b/java/ql/src/Metrics/Files/FCyclomaticComplexity.qhelp new file mode 100644 index 00000000000..2009e77d66f --- /dev/null +++ b/java/ql/src/Metrics/Files/FCyclomaticComplexity.qhelp @@ -0,0 +1,77 @@ + + + + +

    +This metric measures the average cyclomatic complexity of the functions in a file. +

    + +

    +The cyclomatic complexity of a function is the number of linearly independent execution paths +through that function. A path is linearly independent path if it differs from all other paths +by at least one node. Straight-line code therefore has a cyclomatic complexity of one, while +branches, switches and loops increase cyclomatic complexity. +

    + +

    +Functions with a high cyclomatic complexity are typically hard to understand and test. By extension, +files whose functions have a high average cyclomatic complexity are problematic, and usually would +benefit from refactoring. +

    + +

    +As a concrete example, consider the following function: +

    + + + +

    +The control flow graph for this function is as follows: +

    + +Control Flow Graph + +

    +The graph shows that the number of linearly independent execution +paths through the function, and hence its cyclomatic complexity, is +3. The three paths are: +

    + +
      +
    • start -> iEven -> end
    • +
    • start -> iOdd -> jEven -> end
    • +
    • start -> iOdd -> jOdd -> end
    • +
    + +
    + + +

    +Functions with a high cyclomatic complexity should be simplified, for instance by +tidying up any complex logic within them or by splitting them into multiple methods +using the Extract Method refactoring. +

    + +
    + + +
  • +M. Fowler, Refactoring. Addison-Wesley, 1999. +
  • +
  • +T. J. McCabe, A Complexity Measure. IEEE Transactions on Software Engineering, SE-2(4), December 1976. +
  • +
  • +Dave Thomas, Refactoring as Meta Programming?, in Journal of Object Technology, vol. 4, no. 1, January-February 2005, pp. 7-11. +
  • +
  • +Wikipedia: Cyclomatic complexity +
  • +
  • +Wikipedia: Code refactoring +
  • + +
    +
    \ No newline at end of file diff --git a/java/ql/src/Metrics/Files/FCyclomaticComplexity.ql b/java/ql/src/Metrics/Files/FCyclomaticComplexity.ql new file mode 100644 index 00000000000..080807fcdd0 --- /dev/null +++ b/java/ql/src/Metrics/Files/FCyclomaticComplexity.ql @@ -0,0 +1,19 @@ +/** + * @name Average cyclomatic complexity of files + * @description The average cyclomatic complexity of the methods in a file. + * @kind treemap + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg max + * @id java/average-cyclomatic-complexity-per-file + * @tags testability + * complexity + */ +import java + +from CompilationUnit f, float n +where n = avg(Callable c, int toAvg | + c.getCompilationUnit() = f and toAvg = c.getMetrics().getCyclomaticComplexity() | + toAvg + ) +select f, n diff --git a/java/ql/src/Metrics/Files/FCyclomaticComplexity_ControlFlow.gv b/java/ql/src/Metrics/Files/FCyclomaticComplexity_ControlFlow.gv new file mode 100644 index 00000000000..dfccd064080 --- /dev/null +++ b/java/ql/src/Metrics/Files/FCyclomaticComplexity_ControlFlow.gv @@ -0,0 +1,11 @@ +digraph +{ + start -> iEven; + start -> iOdd; + iEven -> end; + iOdd -> jEven; + iOdd -> jOdd; + jEven -> end; + jOdd -> end; +} + diff --git a/java/ql/src/Metrics/Files/FCyclomaticComplexity_ControlFlow.png b/java/ql/src/Metrics/Files/FCyclomaticComplexity_ControlFlow.png new file mode 100644 index 0000000000000000000000000000000000000000..f5c409f8bff99f3e2905827e9927e8f0a1e6d4ee GIT binary patch literal 2668 zcmV-y3X}DTP)|A3r}o-{0TU)6)O|0RR90YrC_X000UDNkl4(Gk@+K zq+}&ol!l`b$u!wR*0x0Q<8VlkGaT^^r}gjGH?KgiKrw(YAs~4AhbaJ+F@{0%0-_oM zKwKb#?q5OzQBw;+JRoY;7>ET#E)9k_KyGa@)*~qpC}cMzP-~$O0SMHz!vJ*@3BiG& z7q2}~XMqqJsP_%VK%D|GY%>uM8r)$CL?*Yq0tE!BD#-5X(`*PO@&}qfe%NDtpzHP7 z9$@=<{XTnp{k!rEI}*_R^n`);7xx>2KTZ>n&~*8bkCMQ!qy&IYJ~0Dx_9HNG3P9WG zB?I&5A8~<7gUt1Yf$wg)4isx({AX5>qyn_M-|+m;yVV^mf%jjJA_09@kpE3D`OC-c z^!?cgFgu^vtI#u@4<$EaOm0q{+^QqJby;#N*Ywu*$yqZxC^OAZ+?C0U%BVh+4nC0v)yyz5;bL5k&?#K$O}JD*{3N(aeG_0TPr&5DrKb4gg_+I*AN}7-T?Q zPT=4`DM^SxpdS<%NYu+99FU~$L4hRsP(UD=as&gC7DPr`?FK5IVB(4wK4KaGhyXNE zWC#|qXNN%5{zBr+D>v$q>Oc*SZ;>zj;3lmHy$a;e%x=YQN~aT21;|;ej_NuttAtbr zYO7M)HElh*fZF!~f##B8YteV0t|kY~ejR_JC!p@6;LoEVdIAz8L`{jH1QLxknhiw0 zA}WAn$v{&csDNat8cGLEqHZ8*BG8lu8X)1_s+|O+01``8wbuf@f#h`{jebBtAYDUu z0+s9ebNkX6T}A!O{3rM~GxOE#@sADA7M45~0$~~G%g5~af{TY9XroYmeRH4*&{ijK z<9%Lows}-6>ILMXgX9FdUorFO)EX#z%fO5bu$&3xfPuHj;}zfh^D0~HdQE(v+MB(q)yxeoFC?=!;3?}A5@^NT>N|^uYF;bUP zz<*(xy*uK&Ti(JSKGuNT&oWr%BlY-V1ocr&S)Go4R+0?eOf_vgny zg^P9f+1Ie_N^^K()b8 z<_;z32t@0OQFPb4K_n6>b-Ay#K+WRl9*#O4{7NAA&JRRbS7f_>C zYv1QB*+!!PqR!}hZ=Mp70!aHBf7w$GX4f6$o$$$=C=@>xGCDuNOqQVHmwBI3=zp@76<^#dw@ z=?Fw4*SGzG@8+8$<~#^3dus;N9*E|Y?0%7*PkuRHaz0n7Jo!-a?ihE}QEMPtRck6= z_7}`Ii^qF<0inm3$TH4MeK}p&O_esmB*1*siZiOG_b;L#u0nXmx9p^J!05Jj?FSkAL9f zPal>Q74-xn={W744^jIL2;ZYGzkgoK9~>1xnzdHK_q5VSx$J&_+nU5Vr~WWLawP%{ z&|}6A>!Y?L?p4RxhxST%pXVBVUB>Q!Z65*Ity~W@H1^v5u*<}?el*lLu6?Mn31V8% zn%XR?jj<6ZrWMxJZKBz!-Thc)$*10yd{>*X&exHW(y>_G30ylMMk24K!lb+20L9XHZ)lRnQZxzm5O~chyrA?ZBUq@>B~Pt0oiOl z6&8!VQpW)@+P*BzQ?PXi69cje5)kHUdK5$71Jy)8I1G?8U)0tl5)k1~K*|6zyZAC` z)}|-a^gFFo0)&YHS%#HLi$*hz=ALH)3xG^xrKS0!U4sn{ghK<7U#Z5gNRSq8dS0I| zkXMku^=meI17QH5rb0{)#qI-D{4lDmQGkI5o9;oh3b!;QT za6lvhQlF928>l&+MSm2H0;uk-Y68NbKy>gXSvvu#fT}0nH34C0pt6&t4urh~qywTB zcLhPeLkv|w)s9{V!d|C6sg$}6sP9>C8lc)xSao>C0IG%35xAunPN~7QRj%)WfhwR{ zH#-1=;6P9QOsc)vk|0Wxap9FN%PIfd|3F185Chho(0~@F7pj38Jof|!X9cQ(nqOAk z|D-@I5Sh;G4S=*J;=#JTr{snPGI|@68BnVjAOaAmX@>#oC=$X0^}0L&(4c3zYgVqE z+qyyn^}fLvXxOvd{||tgRj3>$#$l(h%^E^rq(oLg{0h`qS~mZb$CXvX#KHoVOaAhH zG=bH4Q413zj3uG7<;{YS>S1E>nrsR%BRmL#iU$OohjG=y#3D*)nFGa@P)TFYj + + +

    +This metric measures the number of outgoing dependencies for each compilation +unit, i.e. the number of other units on which this compilation unit depends. +

    + +

    +Compilation units that depend on many other units are typically subject to +more frequent recompilation, because they have to be recompiled every time one +of the many units on which they depend changes. It is particularly problematic +if such a compilation unit is also heavily depended on by other units, because +any minor change to one of its dependencies will trigger a ripple effect of +recompilation throughout the code: such units are known as hubs, and can be +responsible for greatly increasing the time needed for incremental +recompilation. +

    + +
    + + +

    +Unit-level efferent coupling can be reduced by splitting the unit into pieces +along its dependency fault lines, as illustrated by the following diagrams. +Initially, the compilation unit has many incoming and outgoing dependencies, +but it is not the case that the entire unit depends on every dependency. For +example, sub-unit Y only depends on O2 and +O4 (note that Y is not necessarily a class, just +a part of the compilation unit): +

    + + + + + + + + +
    Split Before
    Before
    + +

    +This suggests the following refactoring, where the unit containing +X, Y and Z is split into three new +units, each with fewer dependencies. Notice how the efferent coupling of each +of the new units is significantly lower than that of the original unit: +

    + + + + + + + + +
    Split After
    After
    + +

    +Performing this refactoring clearly relies on an ability to determine suitable +places at which to partition the compilation unit. This can often be done by +simple inspection of the code, but for more complicated units there are +scientific approaches as well. As a simple example of this, consider a +compilation unit that contains multiple classes. This could be partitioned by +visualizing the dependencies of the various classes and grouping those with +the same dependencies. Each group would then be turned into a new compilation +unit. (Of course, for classes it is often best to just have a compilation unit +for each class; this example is merely intended to illustrate how one might go +about partitioning a unit in a mechanical way.) +

    + + + +
    + + + +
  • +M. Fowler. Refactoring. Addison-Wesley, 1999. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/Files/FEfferentCoupling.ql b/java/ql/src/Metrics/Files/FEfferentCoupling.ql new file mode 100644 index 00000000000..f92753b4f69 --- /dev/null +++ b/java/ql/src/Metrics/Files/FEfferentCoupling.ql @@ -0,0 +1,22 @@ +/** + * @name Outgoing file dependencies + * @description The number of compilation units on which a compilation unit depends. + * @kind treemap + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg max + * @id java/outgoing-file-dependencies + * @tags testability + * modularity + * maintainability + */ +import java + +from CompilationUnit f, int n +where n = count(File g | + exists(Class c | c.fromSource() and c.getCompilationUnit() = g | + exists(Class d | d.fromSource() and d.getCompilationUnit() = f | depends(d,c)) + ) + ) +select f, n +order by n desc diff --git a/java/ql/src/Metrics/Files/FEfferentCoupling_SplitAfter.gv b/java/ql/src/Metrics/Files/FEfferentCoupling_SplitAfter.gv new file mode 100644 index 00000000000..9bff9d8deee --- /dev/null +++ b/java/ql/src/Metrics/Files/FEfferentCoupling_SplitAfter.gv @@ -0,0 +1,18 @@ +digraph +{ + rankdir = "LR"; + Z; + Y; + X; + I1 -> Y; + I2 -> X; + I3 -> Z; + I4 -> Y; + I5 -> X; + Z -> O1; + Y -> O2; + X -> O3; + Y -> O4; + Z -> O5; +} + diff --git a/java/ql/src/Metrics/Files/FEfferentCoupling_SplitAfter.png b/java/ql/src/Metrics/Files/FEfferentCoupling_SplitAfter.png new file mode 100644 index 0000000000000000000000000000000000000000..5bf7ed3a94bef95f6a640215bd4dda8f07b96cdf GIT binary patch literal 3449 zcmV-<4TkcGP)%c00960|IeSBjsO4v0d!JMQvg8b*k%9#4F*X> zK~#90?On}o9JvwRS4aRukZuVa2bkLe_QAR>f`wrnlLZXxm`fnvynuHhzr`wE3DhYw zQcGw)WoD+kN6lh2S!9tcGEp7$Gfj4XL$b+Y)mO#ubnWt=@1zlU`hR86&MYOcHh>}+jEFmoAvS-XAtpTmB;u5acQCvpMVY! zms?BvojE{s^rvuFPY_*AB|IEQh;9_JT|Gi{gOF{;5uyh}1rU7`i|E8NK=chZ(ViY4 zdJ%|2^!KA=PqzZm9Ew|o`yV%-%wm#*aSzda{#w1bRNp|kx*%%x@cHjIpj>SbA5S_s z4b=v5t#xIs2;YTm3wGDq`ba)x}(a*Xj@3HSm-1nfMxQ0vPOKW=D%dY(QUZLF@MJUBq7(!VXZ-)JWe) zQ!#ShEVOQbHG+VVV<%s%8)DP(JegxBchwEEjP(wX96PzQZm4~6@yQ%Jxw~$-l@`ay zv6Fl1M%cO!-*5(=V<-33jqw;vBl77Q$4>688|QZ!Kpx(3j-A|JH`aT#yQHSsfnz84 z*Nyi9Dm_iJ96LFoF2j?$FXTnRF;S}pi2It(`%Z<%@4*M*uFLT1_hVag?BtZX42a`+ z;MmDYb@>oE8at^jpT3b`T5%U(L*y8tX?6b}AezHzqtw?sWf~``%i}-QE3J>Nj1By* zE*m0v{?Wg+{@01K$P+f>!iUe@yD#0RixsS!8e*qTy1%%K=z{`cT=?s`?yeqmZ(24p zK-BM7n{!W*VZORGFWm2Z6c<10k7wB-ZmwVYicC|gabffO_Q&x`zqaafAzJT3r%rJ| zh|KFg?mD>tx&hTyhE~*IykkYMR@{%l?H4{W9RWEyy89VA8 zq}SyZT$sipa7yKX_}cDi45D>mc%#p>zDf=eC@c4;dIMaX$m*hyy8C%i>$Ook$?5{u z+2eD)RzJE*EM3{JE{|_MoWc@`A@=HlRpNhJUmRb{nze4~#vtPZX$MH(M=ObaG}#(4ZS@OVBLZwX{b3*$wdn-dtGDeZVHpdsSMR_H*ca*c z>Y;g`8o88}mTa#+7?PhpDr4)Wwgm2bDd91oj;E#3S-|f?d@J47@8y5pLh@eG<~HYZ zGy4EZD+JN}oyn6*Sf;2wm&+gR)i_)})XdThH=+TC31_j-F0GKJ5dfX}uQ)`D*L|!^ zvxPkK&V2q_oxD}QfpT?0+&1bLt6Xgm*DI@BZ4e)BzK7+6AzHldjj{~Z)$zK2x~x46 zLhDUs1bzf!^LkOni;DdELK&V&V`R&=*_Kx*Lc| zZ6LC@h4|3-Qw(9n_QivXCDKY;!0*x@24c9ayO8(l31Xnf zpqvkI-UuOnmvWv|rIpbdj_1nwqjwmL@D!>Ku$WgL4e_CPQp<58Zf8Huhhe297$$)u zT!5z&dcX2)r{Hf~oHG{QC#9Kp5w|MLUyS*(Wm%w6-BFZF8#iFmGm@9TPUMhI%jw5x-xfQtt z5OXVX@eupvOP-p6vJeLdAwFeEnte3>_B?^D$f0q7C;Avd-2XIGnBHmFnpNd0a(EnI z57ntdiT==-(_h&Yxfp$M%-uCwS-sEZDsqupcYnwx2QvpMYbxX_a`Af?H051)xzhSq z!|BadASNqh(ud=#3H8btf)J=Jo_Uo$x-Aq z$EKrjE8+2%&#)vzk=qH^GG;yD?&fsqS)n(F-m6&qBCOU~nRDY6Ip_n7w9?x1g};b} zVnHlVYMiZ`2Qkzi9py0~K(uG(!(A!gpY3;ni0;*yg{Oy$5R!Wk6@(iR;k~ksBC@SH z)8|iDdcv_Q`*ODy#0Vc?kft-i>=)*JOAv@U+>;urFYfL{$nBYTT0fc)Z<7!Yq7^xezo;+F@@XK(DRS^_8Cjs+We$ijikz(_ja=Hh z_Zc9P=s>v4uZ@i44}D^YJ(UiG*PdS-dEKu8L?*9$O4yx5ksDg^^D1#G(&<2ch{IU? z^GObf!(TYUfca>6qu;|-jSG=Z2l7Ll=EV%HINg!0$c?Lu36Z^lT%ld^SUz-fD`k&vrv?i^13sTs1x$Kv(dbh@wzjT zlVy?NA$ahCm`O5bFHi(FKu=@5JyV$x+A18uRBJ{^17p> zD6czC((}5LOIluc7D>qK&Lyotw0PaX%~inyXg+_(ynBR@>Vmjy)QjpNLN`~15TTn( z9HPzZhHfrVO#*L4=;jh{MGN8@y1D9uxPfl2`XKi9R#6%vf!8e!(dKnq5X&L51m|_v z2eAQtu?4NWfxSxuJHQt9(G6H5p3iq-i9p_nP?5;K5hx-#Ou|GY6HJ)Mcvsg5E9}*mPDi z#Oc_NiXl#ir_==z3sr;=nW2hoslOFl8;cFR(Us1NSg7K0U8@VSMuXg3;%jsRnoa}y zVhdV#1ACVSc7QGHqieTD*!k#85w__xa0E?2&Iz%OrstfHg*g0$Ua!E`J!AJ(>M`i& zZ*!G?mu(}N*;&o26?m_9-DpgS)%gJT1I^+=*W^hZz+sb3fIm8`gfc?PLk#Au6o(kj zswoXIg7;GxVhl5>EW{}8R8fd=?5^}AnB}^W{Ij%FB4^!LhFn5ImZxqshc6wi%~Lm? zWtfZ{=u7Aj&p=B>vCNpzArVnEXF`WGbl?2vySVS;Z-TrD9gb^%n2P5 zghAvEp-2!+k~w2!xOYFsMjj*CA zkFL%mA?}`W6Asa9(=k6pKUBV-lc0Zt7$|7>91y;Z*%5O1aS(b(cS@E4J@1ZAud z$lEfoKz~>*Ld<8=LF+CaLG$Y7`$dTPEMvra10(ro1vJ&$BKAdEwUO8T)1|2$7`*O$ zR@z0eTk)&j+`uA5Y~2)y6R#U+swTvo9s@tbiPx=Nt(evC;)OWzy4TmRNHOmfCid#` z_~vM3+{J>J(+7yvF#g-E(GThut3*Lhst@8)iIV2c$*S##Y@VZ0g8Da`) b0pfoFRIkf8g!F@|00000NkvXXu0mjf@Ew}6 literal 0 HcmV?d00001 diff --git a/java/ql/src/Metrics/Files/FEfferentCoupling_SplitBefore.gv b/java/ql/src/Metrics/Files/FEfferentCoupling_SplitBefore.gv new file mode 100644 index 00000000000..9df09272dad --- /dev/null +++ b/java/ql/src/Metrics/Files/FEfferentCoupling_SplitBefore.gv @@ -0,0 +1,16 @@ +digraph +{ + rankdir = "LR"; + I1 -> F:y; + I2 -> F:x; + I3 -> F:z; + I4 -> F:y; + I5 -> F:x; + F [shape=Mrecord, label=" X| Y| Z"]; + F:z -> O1; + F:y -> O2; + F:x -> O3; + F:y -> O4; + F:z -> O5; +} + diff --git a/java/ql/src/Metrics/Files/FEfferentCoupling_SplitBefore.png b/java/ql/src/Metrics/Files/FEfferentCoupling_SplitBefore.png new file mode 100644 index 0000000000000000000000000000000000000000..f29c8f1822f1e7e8e73e3c23bed0893e613c2e85 GIT binary patch literal 3586 zcmV+d4*l_oP)%c00960{~od*tN;K20d!JMQvg8b*k%9#4Ub7g zK~#90?VZ7H+(r_H^9;Ku`Q{C>0p^x0U_-v!fpzsF3;VcE4xVEM@IrbEOK}476pbVf z*iX5`AvwdYu5MPb|DwXcbug?^v+A4fCcC@(XX`8XXSYATdTfGG`+|tD#Uj>oW2EAS zh(&LuJ(4IcL9B~{{lB%P;`)fS|I2zV#$;R%F>Kp;T00$gh{)AhJpZcHZ3iSyA(vfOYA4U zKixbbMNL}TkNdacsdJm_HH~{f^yBY}kLSf_;>`rnbNc>y_2yai?Weg5B@ zlsA-lPQPBe>iX8zFK7`K@$Rakj{|eWho;@mesmw+(IPD3&birZar@i;pUahd85C&{ z>AkS8!RnI>K7*SV_pWK+`o*@mpmEt+bne#g5i``{p%3n%H=k&c2;zsUgg2Nmwdfvh zXpsow)08)uaea~8s8PiGm0QQX!7RL^_qX#kEy9d0`yb!9hn4ry45Q28xws!Vyg}nQ zy7*(|uK3@64D?698OG?t=yLb!{RJ(-Ah!9bb=(=ue6P(IeP4VNlvv7qaS5~T1*}~b zumW7dI(h-why~RRg6}wmLUYvC` z9-_$i0NYE*j;3uj7Cn%3C$+OW`UBd0B;kH^V3vrIeQ$~Iz;1XsH0)R*kv77rH`afC zL{U4-5Y2jH4MQ}o#?dk|M6=~p2}881##%B>5h5B@V=4LaQbCqUHOjvDqaw`$Sk1y|z&5XJ5kh~SpCK*Uy~ z{0lD@k-r|=((L!m)^*z=VszcWmL^{prt3CC#Ob<$E$z7i>bil5L$^p>w+$jz*A458 zHTz$_h;HA-01>V0Hb9i`Udi?VVV&{l5w7coBjR=4a6|)LH>finzp8v!U#yJ)BURZcu0J-NAI`1sLZLz^oW73`?9zuMa51<)_G%e-9%9y)pcLJ zE`H~oV(u5VE4I3Bswj_2ET08l?GamDH&v9!5O=Hj5nC1L8>%ReA>Q8nV2a2&YqhlB zQbl!ynGsIL3x^Vn+;kXxFo#a`D<6y?#|#cg8(elx_r7Kx%f zju?Fn0)fa*N_t&4QIy9Kqpv}jA$GcMswgkn(WUuuM_z+C^=!cXaztcw-9%9y)pc)P zZC2h#vgME(G1PVMh@w29>;5k06@eI8tuCYJ7HQJPf%BRcl|lF`v7QMi_ztEW4EvG0$uM<#z!ym0B@i2d`W zK1G&qh=hgfv6aEWl!$$YERVE;;V`4EghsvsnvNI=|1xZ84pFhxMI%03xGmkB<8Tzq zNHpT*+U<%T(`B6Q9a1o2U3_+D^hp#Vsxlhzz9=fA$`)vJ-BU}3t8$e$jjmhlbgT^P z8Kdh4Au?_)&+DFm$XsH2UiSpV(53Bp-Fn2FEbXK-O!SChjg7?3JqbpQ7y!w43?`nZ zQx^hD0ok9|t*u4P@__7KO+DL5i74E3@#5y5kflTnR)O-J)XC?J=@5f32Dbm8>2+M?3CW|{CA~y#U zUCQgatAD>BMPp48Zs{GiMwv2<2S(TJZvE}8GN$Xkdp({DJY~HHR-Lb$``4O5^l%Es z=|Oqt++DD#6OmX7(v|HZgSOR&-&{pi;WZ`fu4Q!H&YeLKn?Mxt_S)UQ3lT{%Cd1pQ zK&x{wVo?M={`}o9^rP@dnr@CZ{It{3-oIrLne(~>M9Ez6V3*IH2Z*t2he0|;@_Q0F zs_T~5Vt{yma}gj$uH|&yFKhdTu7dPp43X1yKVJb6>jkk7ySybf2`#p+UUBSUp^DjM%V2J%|x_2ulw{tCZgGS-Kjhh7b^8jd1g$LU(~*G zkSt?!Ubi>RQ>H*A{>kSPa@}?G2PK3s?t~qKU=SU%u`&Q%cPbzPQL*phM7vj`Q=q^E zd69fwcRU^j5uxio+4x*{3Sug*c0Zc_28S3esomA5UlEJQnpOomCH%aI>V&$qOv03M z-_cK+{!ZVmo)vNILGIkAwY!r@i4>;Ob%!6(@E45qZbn3Z{=g=ZU6UzTr>@hR2GO5C zw3Dnbe9+D_B9i$-EbSzfSc*-7$R>jsq3W7`R0}4y2~)i9LVoTBRC<$d0}*JO7Ko4f zVLPTb+T?YUW8ZVrq+Hv>Lg>8b%RfdciUhjW1kF^QS_@^gfX)!+WG^7-fA@f;h73 zOV>rTa4mYn&dCtRR(+|uNbJ4zcY@4zj!Vpr|XtB^p2{l!H9it5!7M~aZAVt~jT|7DIia!1Q`-H;o4xDj(Qt*$$E#t{b5abLL8b^D6)$TXrEqaOjW9ivG? z?}#X$FJciD^Tj3nl^pTf1&pXzyNFkSU_`|VP`r)?As+qUb={!5h3pWG#MR|=;D~r# zHy%-u8I$D~QC-pB#2SgK%hDPkj>pyIx-Af~x^7rk^uH`p*KLG|)OEw!nSNWGuGqa8-Eh5~HR(hRka%Yc*C~uOWTxiE@9BhbXSb z=~~)sgCoK#QS5Q5mKI?`mwzEdd^?MMN)ZhzQR?(j5oOY3sc1TfB<@x8#f6BIU$aPC zDQTW*qG^KEx*-!a6>eR!AhhO(j4Rt5T>RahjVj(?f*2D72lR!jTz=7*pNuj}3he|OMg zCc)^szb{DSsIJ>>=y@*Ywb<#pe?I?Ai$o9~=qXp`h`p|Rdqayv5GzCF2Jf`jb(6?( zM0f3fG}j%ye~|FC80xxriq>lv>;@3-FL4xD_Vlrbr4Qs#?Gm~}5;?XrLs;1br+3&=(+Ag8m0%-9n0iwnroE+N+~JqZSH zNL{y;q0j5KFzk8VHU>Sf+sq)kZhOntbw84QK}6UWM1=kEe^$Y}@{j0_iU0rr07*qo IM6N<$f@n|=7XSbN literal 0 HcmV?d00001 diff --git a/java/ql/src/Metrics/Files/FLines.ql b/java/ql/src/Metrics/Files/FLines.ql new file mode 100644 index 00000000000..94288e0946b --- /dev/null +++ b/java/ql/src/Metrics/Files/FLines.ql @@ -0,0 +1,15 @@ +/** + * @name Number of lines + * @description The number of lines in each file. + * @kind treemap + * @treemap.warnOn highValues + * @id java/lines-per-file + * @metricType file + * @metricAggregate avg sum max + */ +import java + +from File f, int n +where n = f.getTotalNumberOfLines() +select f, n +order by n desc diff --git a/java/ql/src/Metrics/Files/FLinesOfCode.qhelp b/java/ql/src/Metrics/Files/FLinesOfCode.qhelp new file mode 100644 index 00000000000..79fa708599a --- /dev/null +++ b/java/ql/src/Metrics/Files/FLinesOfCode.qhelp @@ -0,0 +1,40 @@ + + + + + + + + +

    +The solution depends on the reason for the high number of lines: +

    + +
      +
    • +If the file's main class is too large, you should refactor it into smaller classes, +for example by using the 'Extract Class' refactoring from [Fowler]. +
    • + +
    • +If the file's main class contains many nested classes, you should move the nested classes to their +own files (in a subsidiary package, where appropriate). +
    • + +
    • +If the file contains multiple non-public classes in addition to its main +class, you should move them into separate files. This is particularly important if they are +logically unrelated to the file's main class. +
    • + +
    • +If the file has been automatically generated by a tool, no changes are required because the file +will not be maintained by a programmer. +
    • +
    + +
    + +
    diff --git a/java/ql/src/Metrics/Files/FLinesOfCode.ql b/java/ql/src/Metrics/Files/FLinesOfCode.ql new file mode 100644 index 00000000000..689ea6e0376 --- /dev/null +++ b/java/ql/src/Metrics/Files/FLinesOfCode.ql @@ -0,0 +1,18 @@ +/** + * @name Lines of code in files + * @description The number of lines of code in a file. + * @kind treemap + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg sum max + * @precision very-high + * @id java/lines-of-code-in-files + * @tags maintainability + * complexity + */ +import java + +from File f, int n +where n = f.getNumberOfLinesOfCode() +select f, n +order by n desc diff --git a/java/ql/src/Metrics/Files/FLinesOfComment.qhelp b/java/ql/src/Metrics/Files/FLinesOfComment.qhelp new file mode 100644 index 00000000000..914c4808bb3 --- /dev/null +++ b/java/ql/src/Metrics/Files/FLinesOfComment.qhelp @@ -0,0 +1,55 @@ + + + +

    +This metric measures the number of comment lines for each file. +

    + +

    +Whilst the absolute number of comment lines in a file may not provide much +useful information out of context, a very small number of comments in a file +may indicate either a potentially worrying lack of documentation or that the +file was generated by an automated tool - a quick visual inspection should be +sufficient to distinguish between the two cases. +

    + +
    + + +

    +If the file is not an auto-generated one, it should be documented at the first +convenient opportunity (i.e. now). We note in passing that: +

    + +
      +
    • +From a pragmatic standpoint, it is clear that not all files are equally +important to document, so some common sense needs to be applied when deciding +which code should be documented first. +
    • + +
    • +Documenting entire files after the fact is not only onerous, but also often +yields lower-quality documentation than would have been written by the +original author at the time of writing the code (because other developers +may not understand the context as well as the person who wrote the code). +For this reason, finding completely undocumented files should be treated as +a sign that your documentation practices in general need to improve. +
    • +
    + + + +
    + + + +
  • +S. McConnell. Code Complete, 2nd Edition. Microsoft Press, 2004. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/Files/FLinesOfComment.ql b/java/ql/src/Metrics/Files/FLinesOfComment.ql new file mode 100644 index 00000000000..151e32f8ea4 --- /dev/null +++ b/java/ql/src/Metrics/Files/FLinesOfComment.ql @@ -0,0 +1,18 @@ +/** + * @name Lines of comments in files + * @description The number of lines of comment in a file. + * @kind treemap + * @treemap.warnOn lowValues + * @metricType file + * @metricAggregate avg sum max + * @precision very-high + * @id java/lines-of-comments-in-files + * @tags maintainability + * documentation + */ +import java + +from File f, int n +where n = f.getNumberOfCommentLines() +select f, n +order by n desc diff --git a/java/ql/src/Metrics/Files/FLinesOfCommentedCode.qhelp b/java/ql/src/Metrics/Files/FLinesOfCommentedCode.qhelp new file mode 100644 index 00000000000..d435289b652 --- /dev/null +++ b/java/ql/src/Metrics/Files/FLinesOfCommentedCode.qhelp @@ -0,0 +1,7 @@ + + + + + diff --git a/java/ql/src/Metrics/Files/FLinesOfCommentedCode.ql b/java/ql/src/Metrics/Files/FLinesOfCommentedCode.ql new file mode 100644 index 00000000000..c5f4edf1c14 --- /dev/null +++ b/java/ql/src/Metrics/Files/FLinesOfCommentedCode.ql @@ -0,0 +1,19 @@ +/** + * @name Lines of commented-out code in files + * @description The number of lines of commented-out code in a file. + * @kind treemap + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg sum max + * @precision high + * @id java/lines-of-commented-out-code-in-files + * @tags maintainability + * documentation + */ + +import Violations_of_Best_Practice.Comments.CommentedCode + +from File f, int n +where n = sum(CommentedOutCode comment | comment.getFile() = f | comment.getCodeLines()) +select f, n +order by n desc diff --git a/java/ql/src/Metrics/Files/FLinesOfDuplicatedCode.qhelp b/java/ql/src/Metrics/Files/FLinesOfDuplicatedCode.qhelp new file mode 100644 index 00000000000..30a98df0cee --- /dev/null +++ b/java/ql/src/Metrics/Files/FLinesOfDuplicatedCode.qhelp @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/java/ql/src/Metrics/Files/FLinesOfDuplicatedCode.ql b/java/ql/src/Metrics/Files/FLinesOfDuplicatedCode.ql new file mode 100644 index 00000000000..0d3c44a9052 --- /dev/null +++ b/java/ql/src/Metrics/Files/FLinesOfDuplicatedCode.ql @@ -0,0 +1,25 @@ +/** + * @name Duplicated lines in files + * @description The number of lines in a file, including code, comment and whitespace lines, + * which are duplicated in at least one other place. + * @kind treemap + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg sum max + * @precision high + * @id java/duplicated-lines-in-files + * @tags testability + * modularity + */ +import external.CodeDuplication + +from File f, int n +where + n = count(int line | + exists(DuplicateBlock d | d.sourceFile() = f | + line in [d.sourceStartLine()..d.sourceEndLine()] and + not whitelistedLineForDuplication(f, line) + ) + ) +select f, n +order by n desc diff --git a/java/ql/src/Metrics/Files/FLinesOfSimilarCode.qhelp b/java/ql/src/Metrics/Files/FLinesOfSimilarCode.qhelp new file mode 100644 index 00000000000..3efe6ad6681 --- /dev/null +++ b/java/ql/src/Metrics/Files/FLinesOfSimilarCode.qhelp @@ -0,0 +1,31 @@ + + + +

    +A file that contains many lines that are similar to other code within the code base is +problematic for the same reasons as a file that contains a lot of (exactly) +duplicated code. +

    + +
    + + + + +

    +Refactor similar code snippets by extracting common functionality into methods +that can be reused across classes. +

    + +
    + + + +
  • Wikipedia: Duplicate code.
  • +
  • M. Fowler, Refactoring. Addison-Wesley, 1999.
  • + + + + diff --git a/java/ql/src/Metrics/Files/FLinesOfSimilarCode.ql b/java/ql/src/Metrics/Files/FLinesOfSimilarCode.ql new file mode 100644 index 00000000000..da953436c9a --- /dev/null +++ b/java/ql/src/Metrics/Files/FLinesOfSimilarCode.ql @@ -0,0 +1,22 @@ +/** + * @name Similar lines in files + * @description The number of lines in a file, including code, comment and whitespace lines, + * which are similar to lines in at least one other place. + * @kind treemap + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg sum max + * @id java/similar-lines-per-file + * @tags testability + */ +import external.CodeDuplication + +from File f, int n +where n = count(int line | + exists(SimilarBlock d | d.sourceFile() = f | + line in [d.sourceStartLine()..d.sourceEndLine()] and + not whitelistedLineForDuplication(f, line) + ) + ) +select f, n +order by n desc \ No newline at end of file diff --git a/java/ql/src/Metrics/Files/FNumberOfClasses.qhelp b/java/ql/src/Metrics/Files/FNumberOfClasses.qhelp new file mode 100644 index 00000000000..2d062444a57 --- /dev/null +++ b/java/ql/src/Metrics/Files/FNumberOfClasses.qhelp @@ -0,0 +1,108 @@ + + + +

    +This metric measures the number of classes below this location in the tree. +At a file level, this would just be the number of classes in the file. +

    + +

    +There are both advantages and disadvantages to putting multiple classes in the +same file, so some judgment needs to be applied when interpreting these +results. That said, putting large numbers of classes, or unrelated classes, in +the same file is a malpractice that makes the organization of the code harder +to understand. There is a debate in the programming community as to whether it +is necessary to go as far as having a 'one class per file' rule, but there is +far more agreement over the principle that you should not bundle large +numbers of unrelated classes into a single file. Indeed, Java already enforces +a 'one public class per file' rule - this was done to make importing +packages efficient (see [Kabutz]). +

    + +

    +The disadvantages of putting multiple classes in the same file include: +

    + +
      +
    • +It causes problems with incremental compilation because changes to a class +force all of the other classes in the same file to be recompiled, even if +they don't actually need to be. Furthermore, files that contain multiple +classes generally have higher coupling, which can also make for slower builds. +
    • + +
    • +It increases the likelihood of multiple developers working on the same file +at once, and thereby the likelihood of merge conflicts. +
    • + +
    • +Projects whose files contain multiple classes can be less intuitive to +navigate, even with tool support, because the logical organization of the +code is obscured. +
    • + +
    • +It makes your files larger, which can increase network traffic if you're using +a poor version control system. +
    • + +
    • +It makes it harder to look at the files changed in a commit and infer what +has been changed at a code level. +
    • + +
    • +Some compilers generate error messages based on the file name rather than the +class name, which are less helpful if there is not a one-to-one correspondence +between files and classes. +
    • +
    + +

    +There are a couple of advantages, however: +

    + +
      +
    • +It reduces the proliferation of files containing very few lines of code. +
    • + +
    • +It can be used positively to group logically-related classes together. +
    • +
    + +
    + + +

    +Generally speaking, the goal is to ensure that only strongly logically-related +classes are 'packaged' together in the same file. This usually militates in +favor of having a separate file for each class. If your code currently puts +lots of large, unrelated classes in the same file, the solution is to move them +all into separate files. +

    + +

    +As with any rule or guideline, however, there are exceptions. The primary one +is that helper classes that are only used in the context of a file's main +class should probably stay in the same file, e.g. an iterator class for a +container can happily cohabit the container's source file. The same applies +to enumerations. +

    + + +
    + + + +
  • +H. Kabutz. Java History 101: Once Upon an Oak. Published online. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/Files/FNumberOfClasses.ql b/java/ql/src/Metrics/Files/FNumberOfClasses.ql new file mode 100644 index 00000000000..dcb7186e956 --- /dev/null +++ b/java/ql/src/Metrics/Files/FNumberOfClasses.ql @@ -0,0 +1,16 @@ +/** + * @name Number of classes + * @description The number of classes in a compilation unit. + * @kind treemap + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg sum max + * @id java/classes-per-file + * @tags maintainability + */ +import java + +from CompilationUnit f, int n +where n = count(Class c | c.fromSource() and c.getCompilationUnit() = f) +select f, n +order by n desc diff --git a/java/ql/src/Metrics/Files/FNumberOfInterfaces.qhelp b/java/ql/src/Metrics/Files/FNumberOfInterfaces.qhelp new file mode 100644 index 00000000000..cff4481c447 --- /dev/null +++ b/java/ql/src/Metrics/Files/FNumberOfInterfaces.qhelp @@ -0,0 +1,50 @@ + + + +

    +This metric measures the number of interfaces below this location in the tree. +At a file level, this would just be the number of interfaces in the file. +

    + +

    +In general, it is not a good idea to put multiple interfaces in the same file. +Interfaces are intended in some sense to be publicly visible, and changes to +them are expected to have a higher impact than changes to internal classes. +Making a change to an interface forces everybody who depends on that interface +to recompile their code, which is already hugely disruptive. If multiple +interfaces are in the same file, not only do the people who depend on the +changing interface need to recompile, but so does everyone who depends on any +of the interfaces in the file (since they have a physical dependency on the +file, not just a logical dependency on one of the interfaces in it). For this +reason, a strict rule of one interface per file should be rigorously enforced. +

    + +

    +Note that this should be compared to the advice for classes - whilst it is +advisable to use a single file for each class, it is acceptable to put classes +in the same file when they are logically related (if only then). It is far +less acceptable in the case of interfaces due to their public-facing nature. +

    + +
    + + +

    +Any interfaces that currently share a source file with another interface (or +indeed a class) should be given their own file. +

    + + +
    + + + +
  • +The Java Language Specification. §9: Interfaces. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/Files/FNumberOfInterfaces.ql b/java/ql/src/Metrics/Files/FNumberOfInterfaces.ql new file mode 100644 index 00000000000..1dab5bc745a --- /dev/null +++ b/java/ql/src/Metrics/Files/FNumberOfInterfaces.ql @@ -0,0 +1,16 @@ +/** + * @name Number of interfaces + * @description The number of interfaces in a compilation unit. + * @kind treemap + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg sum max + * @id java/interfaces-per-file + * @tags maintainability + */ +import java + +from CompilationUnit f, int n +where n = count(Interface i | i.fromSource() and i.getCompilationUnit() = f) +select f, n +order by n desc diff --git a/java/ql/src/Metrics/Files/FNumberOfTests.qhelp b/java/ql/src/Metrics/Files/FNumberOfTests.qhelp new file mode 100644 index 00000000000..ca2856c9c51 --- /dev/null +++ b/java/ql/src/Metrics/Files/FNumberOfTests.qhelp @@ -0,0 +1,57 @@ + + + +

    + This metric measures the number of tests below this location in the tree. + At a file level, this would just be the number of test methods in the file. +

    + +

    + A method is considered to be a "test method" if one of the major + unit testing frameworks would invoke it as part of a test + cycle. Recognised frameworks include JUnit 3, JUnit 4 and TestNG. + For example, methods marked with the org.junit.Test + annotation are counted as test methods. +

    + +

    + In general, having many test cases is a good thing rather than a bad + thing. However, at the file level, tests should typically be grouped + by the functionality they relate to, which makes a file with an + exceptionally high number of tests a strong candidate for splitting + up. At a higher level, this metric makes it possible to compare the + number of tests in different components, potentially flagging + functionality that is comparatively under-tested. +

    +
    + +

    + Since it is typically not a problem to have too many tests, this + metric is usually included for the purposes of collecting + information, rather than finding problematic areas in the code. With + that in mind, it is usually a good idea to avoid an excessive number + of tests in a single file, and to maintain a broadly comparable + level of testing across components. +

    + +

    + When assessing the thoroughness of a code base's test suite, the number + of test methods provides only part of the story. Test coverage + statistics allow a more detailed examination of which parts of the + code deserve improvements in this area. +

    +
    + + + +
  • + JUnit: official website at http://junit.org/. +
  • +
  • + TestNG: official website at http://testng.org/. +
  • + +
    +
    diff --git a/java/ql/src/Metrics/Files/FNumberOfTests.ql b/java/ql/src/Metrics/Files/FNumberOfTests.ql new file mode 100644 index 00000000000..f52a27adf74 --- /dev/null +++ b/java/ql/src/Metrics/Files/FNumberOfTests.ql @@ -0,0 +1,17 @@ +/** + * @name Number of tests + * @description The number of test methods defined in a compilation unit. + * @kind treemap + * @treemap.warnOn lowValues + * @metricType file + * @metricAggregate avg sum max + * @precision medium + * @id java/tests-in-files + * @tags maintainability + */ +import java + +from CompilationUnit f, int n +where n = strictcount(TestMethod test | test.fromSource() and test.getCompilationUnit() = f) +select f, n +order by n desc diff --git a/java/ql/src/Metrics/Files/FSelfContainedness.qhelp b/java/ql/src/Metrics/Files/FSelfContainedness.qhelp new file mode 100644 index 00000000000..c3729aabfbc --- /dev/null +++ b/java/ql/src/Metrics/Files/FSelfContainedness.qhelp @@ -0,0 +1,71 @@ + + + +

    +This metric measures the percentage of the types on which a compilation unit +depends for which we have source code available. +

    + +

    +The availability of source code is one of the key factors affecting how easy +or difficult it will be to build a software project in the future, especially +on platforms other than those for which it was originally developed. Projects +will a high level of self-containedness are likely to be more portable and +easier to build in ten years' time than those that depend on many binary-only, +third-party libraries. (This is one reason why many of the dependencies of +open-source projects are distributed as source code, aside from the fact that +the binaries are generally larger and more unwieldy to distribute.) +

    + +

    +In the context of Java's platform independence, the availability of source code +is less critical than it is for platform-dependent languages. +However, note that there can be minor binary incompatibilities between +different versions of Java. +

    + +
    + + +

    +Low self-containedness may or may not be a problem, depending on the context +of your project. However, if you determine that it is an issue for you, it is +best tackled at a project level, in the following ways: +

    + +
      +
    • +Try to use libraries for which the source code is available. +
    • +
    • +Try to obtain the source code for binary-only libraries from the authors. +
    • +
    • +Where practical, rewrite parts of your code to reduce your dependence on +external libraries. +
    • +
    + + + +
    + + + +
  • +Wikipedia. Software portability. Published online. +
  • +
  • + Java SE 6 Compatibility, + Oracle Technology Network. +
  • +
  • + The Java Language Specification + Chapter 13 +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/Files/FSelfContainedness.ql b/java/ql/src/Metrics/Files/FSelfContainedness.ql new file mode 100644 index 00000000000..55a5aa5e3f1 --- /dev/null +++ b/java/ql/src/Metrics/Files/FSelfContainedness.ql @@ -0,0 +1,29 @@ +/** + * @name Self-containedness of files + * @description The percentage of the types on which a compilation unit depends for which we have the source code. + * @kind treemap + * @treemap.warnOn lowValues + * @metricType file + * @metricAggregate avg max + * @id java/source-dependency-ratio-per-file + * @tags portability + * modularity + */ +import java + +from CompilationUnit f, float selfContaindness, int efferentSourceCoupling, int efferentCoupling +where efferentSourceCoupling = count(CompilationUnit g | + exists(RefType c | c.fromSource() and c.getCompilationUnit() = g | + exists(RefType d | d.fromSource() and d.getCompilationUnit()= f | depends(d,c)) + ) + ) + and efferentCoupling = count(CompilationUnit g | + exists(RefType c | c.getCompilationUnit() = g | + exists(RefType d | d.fromSource() and d.getCompilationUnit() = f | depends(d,c)) + ) + ) + and if efferentCoupling = 0 + then selfContaindness = 100 + else selfContaindness = 100*(float)efferentSourceCoupling/efferentCoupling +select f, selfContaindness +order by selfContaindness desc diff --git a/java/ql/src/Metrics/History/HChurn.qhelp b/java/ql/src/Metrics/History/HChurn.qhelp new file mode 100644 index 00000000000..2d248a0ac18 --- /dev/null +++ b/java/ql/src/Metrics/History/HChurn.qhelp @@ -0,0 +1,42 @@ + + + +

    +This metric measures the number of lines of text that have been added, deleted +or modified in files below this location in the tree. +

    + +

    +Code churn is known to be a good (if not the best) predictor of defects in a +code component (see e.g. [Nagappan] or [Khoshgoftaar]). The intuition is that +files, packages or projects that have experienced a disproportionately high +amount of churn for the amount of code involved may have been harder to write, +and are thus likely to contain more bugs. +

    + +
    + + +

    +It is a fact of life that some code is going to be changed more than the rest, +and little can be done to change this. However, bearing in mind code churn's +effectiveness as a defect predictor, code that has been repeatedly changed +should be subjected to vigorous testing and code review. +

    + +
    + + + +
  • +N. Nagappan et al. Change Bursts as Defect Predictors. In Proceedings of the 21st IEEE International Symposium on Software Reliability Engineering, 2010. +
  • +
  • +T. M. Khoshgoftaar and R. M. Szabo. Improving code churn predictions during the system test and maintenance phases. In ICSM '94, 1994, pp. 58-67. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/History/HChurn.ql b/java/ql/src/Metrics/History/HChurn.ql new file mode 100644 index 00000000000..be210606eb5 --- /dev/null +++ b/java/ql/src/Metrics/History/HChurn.ql @@ -0,0 +1,19 @@ +/** + * @name Churned lines + * @description The number of churned lines of a file (by version control history). + * @kind treemap + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg sum max + * @id java/vcs/churn-per-file + */ +import java +import external.VCS + +from File f, int n +where n = sum(Commit entry, int churn | + churn = entry.getRecentChurnForFile(f) and not artificialChange(entry) | + churn + ) +select f, n +order by n desc diff --git a/java/ql/src/Metrics/History/HLinesAdded.qhelp b/java/ql/src/Metrics/History/HLinesAdded.qhelp new file mode 100644 index 00000000000..fc812fc8357 --- /dev/null +++ b/java/ql/src/Metrics/History/HLinesAdded.qhelp @@ -0,0 +1,6 @@ + + + + diff --git a/java/ql/src/Metrics/History/HLinesAdded.ql b/java/ql/src/Metrics/History/HLinesAdded.ql new file mode 100644 index 00000000000..b43f7a5ddf0 --- /dev/null +++ b/java/ql/src/Metrics/History/HLinesAdded.ql @@ -0,0 +1,16 @@ +/** + * @name Added lines + * @description The number of added lines of a file (by version control history). + * @kind treemap + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg sum max + * @id java/vcs/added-lines-per-file + */ +import java +import external.VCS + +from File f, int n +where n = sum(Commit entry, int churn | churn = entry.getRecentAdditionsForFile(f) and not artificialChange(entry) | churn) +select f, n +order by n desc diff --git a/java/ql/src/Metrics/History/HLinesDeleted.qhelp b/java/ql/src/Metrics/History/HLinesDeleted.qhelp new file mode 100644 index 00000000000..fc812fc8357 --- /dev/null +++ b/java/ql/src/Metrics/History/HLinesDeleted.qhelp @@ -0,0 +1,6 @@ + + + + diff --git a/java/ql/src/Metrics/History/HLinesDeleted.ql b/java/ql/src/Metrics/History/HLinesDeleted.ql new file mode 100644 index 00000000000..10d40d44bde --- /dev/null +++ b/java/ql/src/Metrics/History/HLinesDeleted.ql @@ -0,0 +1,16 @@ +/** + * @name Deleted lines + * @description The number of deleted lines of a file (by version control history). + * @kind treemap + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg sum max + * @id java/vcs/deleted-lines-per-file + */ +import java +import external.VCS + +from File f, int n +where n = sum(Commit entry, int churn | churn = entry.getRecentDeletionsForFile(f) and not artificialChange(entry) | churn) +select f, n +order by n desc diff --git a/java/ql/src/Metrics/History/HNumberOfAuthors.qhelp b/java/ql/src/Metrics/History/HNumberOfAuthors.qhelp new file mode 100644 index 00000000000..00ec48b744f --- /dev/null +++ b/java/ql/src/Metrics/History/HNumberOfAuthors.qhelp @@ -0,0 +1,48 @@ + + + +

    +This metric measures the number of different authors (by examining the +version control history) +for files below this location in the tree. (This is a better version +of the metric that counts the number of different authors using Javadoc +tags.) +

    + +

    +Files that have been changed by a large number of different authors are +by definition the product of many minds. New authors working on a file +may be less familiar with the design and implementation of the code than +the original authors, which can be a potential source of bugs. Furthermore, +code that has been worked on by many people, if not carefully maintained, +often ends up lacking conceptual integrity. For both of these reasons, any +code that has been worked on by an unusually high number of different people +merits careful inspection in code reviews. +

    + +
    + + +

    +There is clearly no way to reduce the number of authors that have worked +on a file - it is impossible to rewrite history. However, files highlighted +by this metric should be given special attention in a code review, and may +ultimately be good candidates for refactoring/rewriting by an individual, +experienced developer. +

    + + + +
    + + + +
  • +F. P. Brooks Jr. The Mythical Man-Month, Chapter 4. Addison-Wesley, 1974. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/History/HNumberOfAuthors.ql b/java/ql/src/Metrics/History/HNumberOfAuthors.ql new file mode 100644 index 00000000000..35915981b8f --- /dev/null +++ b/java/ql/src/Metrics/History/HNumberOfAuthors.ql @@ -0,0 +1,14 @@ +/** + * @name Number of authors (version control) + * @description The number of distinct authors (by version control history) of a file. + * @kind treemap + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg min max + * @id java/vcs/authors-per-file + */ +import java +import external.VCS + +from File f +select f, count(Author author | author.getAnEditedFile() = f) diff --git a/java/ql/src/Metrics/History/HNumberOfChanges.qhelp b/java/ql/src/Metrics/History/HNumberOfChanges.qhelp new file mode 100644 index 00000000000..8625b20bee9 --- /dev/null +++ b/java/ql/src/Metrics/History/HNumberOfChanges.qhelp @@ -0,0 +1,30 @@ + + + +

    +This metric measures the total number of file-level changes made to files +below this location in the tree. For an individual file, it measures the +number of commits that have affected that file. For a directory of files, it +measures the sum of the file-level changes for each of the files in the +directory. +

    + +

    +For example, suppose we have a directory containing two files, A and B. If the +number of file-level changes to A is 100, and the number of +file-level changes to B is 80, then the total number of +file-level changes to the directory is 180. Note that this is +likely to be different (in some cases very different) from the number of +commits that affected any file in the directory, since more than one file can +be changed by a single commit. (Note what would happen if we performed +80 commits on A and B, followed by another 20 +commits on A alone - the total number of file-level changes would be +180, but the number of commits involved would be +100.) +

    + + +
    +
    diff --git a/java/ql/src/Metrics/History/HNumberOfChanges.ql b/java/ql/src/Metrics/History/HNumberOfChanges.ql new file mode 100644 index 00000000000..427d5356d13 --- /dev/null +++ b/java/ql/src/Metrics/History/HNumberOfChanges.ql @@ -0,0 +1,14 @@ +/** + * @name Number of file-level changes + * @description The number of file-level changes made (by version control history). + * @kind treemap + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg min max sum + * @id java/vcs/commits-per-file + */ +import java +import external.VCS + +from File f +select f, count(Commit svn | f = svn.getAnAffectedFile() and not artificialChange(svn)) diff --git a/java/ql/src/Metrics/History/HNumberOfRecentChanges.qhelp b/java/ql/src/Metrics/History/HNumberOfRecentChanges.qhelp new file mode 100644 index 00000000000..fa03a183d8e --- /dev/null +++ b/java/ql/src/Metrics/History/HNumberOfRecentChanges.qhelp @@ -0,0 +1,63 @@ + + + +

    +This metric measures the number of recent changes to files that have occurred +below this location in the tree. A recent change is taken to mean a change +that has occurred in the last 180 days. +

    + +

    +All code that has changed a great deal may be more than usually prone to +defects, but this is particularly true of code that has been changing +dramatically in the recent past, because it has not yet had a chance to be +properly field-tested in order to iron out the bugs. +

    + +
    + + +

    +There is more than one reason why a file may have been changing a lot +recently: +

    + +
      +
    • +The file may be part of a new subsystem that is being written. New code is +always going to change a lot in a short period of time, but it is important +to ensure that it is properly code reviewed and unit tested before integrating +it into a working product. +
    • + +
    • +The file may be being heavily refactored. Large refactorings are sometimes +essential, but they are also quite risky. You should write proper regression +tests before starting on a major refactoring, and check that they still pass +once you're done. +
    • + +
    • +The same bit of code may be being changed repeatedly because it is difficult +to get right. Aside from vigorous code reviewing and testing, it may be a good +idea to rethink the system design - if something is that hard +to get right (and it's not an inherently difficult concept), you might be making life unnecessarily hard for yourself and +risking introducing insidious defects. +
    • +
    + + + +
    + + + +
  • +N. Nagappan et al. Change Bursts as Defect Predictors. In Proceedings of the 21st IEEE International Symposium on Software Reliability Engineering, 2010. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/History/HNumberOfRecentChanges.ql b/java/ql/src/Metrics/History/HNumberOfRecentChanges.ql new file mode 100644 index 00000000000..4de946f033a --- /dev/null +++ b/java/ql/src/Metrics/History/HNumberOfRecentChanges.ql @@ -0,0 +1,20 @@ +/** + * @name Number of recent file changes + * @description Number of recent commits to a file (by version control history). + * @kind treemap + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg min max sum + * @id java/vcs/recent-commits-per-file + */ +import java +import external.VCS + +from File f, int n +where n = count(Commit e | + e.getAnAffectedFile() = f and + e.daysToNow() <= 180 and + not artificialChange(e) + ) +select f, n +order by n desc diff --git a/java/ql/src/Metrics/Internal/CallableDisplayStrings.ql b/java/ql/src/Metrics/Internal/CallableDisplayStrings.ql new file mode 100644 index 00000000000..053cee84ead --- /dev/null +++ b/java/ql/src/Metrics/Internal/CallableDisplayStrings.ql @@ -0,0 +1,17 @@ +/** + * @name Display strings of callables + * @kind display-string + * @metricType callable + * @id java/callable-display-strings + */ +import java + +private string prefix(Callable c) { + if (c instanceof Constructor and c.getDeclaringType() instanceof AnonymousClass) + then result = "" + else result = "" +} + +from Callable c +where c.fromSource() +select c, prefix(c) + c.getStringSignature() diff --git a/java/ql/src/Metrics/Internal/CallableExtents.ql b/java/ql/src/Metrics/Internal/CallableExtents.ql new file mode 100644 index 00000000000..245a62f9f79 --- /dev/null +++ b/java/ql/src/Metrics/Internal/CallableExtents.ql @@ -0,0 +1,12 @@ +/** + * @name Extents of callables + * @kind extent + * @metricType callable + * @id java/callable-extents + */ +import java +import Extents + +from RangeCallable c +where c.fromSource() +select c.getLocation(), c diff --git a/java/ql/src/Metrics/Internal/CallableSourceLinks.ql b/java/ql/src/Metrics/Internal/CallableSourceLinks.ql new file mode 100644 index 00000000000..4794a0dd174 --- /dev/null +++ b/java/ql/src/Metrics/Internal/CallableSourceLinks.ql @@ -0,0 +1,11 @@ +/** + * @name Source links of callables + * @kind source-link + * @metricType callable + * @id java/callable-source-links + */ +import java + +from Callable c +where c.fromSource() +select c, c.getFile() diff --git a/java/ql/src/Metrics/Internal/Extents.qll b/java/ql/src/Metrics/Internal/Extents.qll new file mode 100644 index 00000000000..00650aa7fc7 --- /dev/null +++ b/java/ql/src/Metrics/Internal/Extents.qll @@ -0,0 +1,62 @@ +import java + +/* + * When this library is imported, the `hasLocationInfo` predicate of + * `Callable` and `RefTypes` is overridden to specify their entire range + * instead of just the range of their name. The latter can still be + * obtained by invoking the `getLocation()` predicate. + * + * The full ranges are required for the purpose of associating a violation + * with an individual `Callable` or `RefType` as opposed to a whole `File`. + */ + +/** + * A Callable whose `hasLocationInfo` is overridden to specify its entire range + * including the body (if any), as opposed to the location of its name only. + */ +class RangeCallable extends Callable { + predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { + exists(int elSuper, int ecSuper | super.hasLocationInfo(path, sl, sc, elSuper, ecSuper) | + this.getBody().hasLocationInfo(path, _, _, el, ec) or + (not exists(this.getBody()) and + (lastParameter().hasLocationInfo(path, _, _, el, ec) or + (not exists(this.getAParameter()) and el = elSuper and ec = ecSuper))) + ) + } + + private Parameter lastParameter() { + result = getAParameter() and + not (getAParameter().getPosition() > result.getPosition()) + } +} + +/** + * A `RefType` whose `hasLocationInfo` is overridden to specify its entire range + * including the range of its members (if any), as opposed to the location of its name only. + */ +class RangeRefType extends RefType { + predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { + exists(int elSuper, int ecSuper | super.hasLocationInfo(path, sl, sc, elSuper, ecSuper) | + lastMember().hasLocationInfo(path, _, _, el, ec) or + (not exists(this.getAMember()) and el = elSuper and ec = ecSuper) + ) + } + + private Member lastMember() { + exists(Member m, int i | + result = m and + m = getAMember() and + i = rankOfMember(m) and + not exists(Member other | other = getAMember() and rankOfMember(other) > i) + ) + } + + private int rankOfMember(Member m) { + this.getAMember() = m and + exists(Location mLoc, File f, int maxCol | mLoc = m.getLocation() | + f = mLoc.getFile() and + maxCol = max(Location loc | loc.getFile() = f | loc.getStartColumn()) and + result = mLoc.getStartLine() * maxCol + mLoc.getStartColumn() + ) + } +} diff --git a/java/ql/src/Metrics/Internal/ReftypeDisplayStrings.ql b/java/ql/src/Metrics/Internal/ReftypeDisplayStrings.ql new file mode 100644 index 00000000000..181893259bd --- /dev/null +++ b/java/ql/src/Metrics/Internal/ReftypeDisplayStrings.ql @@ -0,0 +1,17 @@ +/** + * @name Display strings of reference types + * @kind display-string + * @metricType reftype + * @id java/reference-type-display-strings + */ +import java + +private string suffix(RefType t) { + if t instanceof AnonymousClass + then result = "" + else result = "" +} + +from RefType t +where t.fromSource() +select t, t.nestedName() + suffix(t) diff --git a/java/ql/src/Metrics/Internal/ReftypeExtents.ql b/java/ql/src/Metrics/Internal/ReftypeExtents.ql new file mode 100644 index 00000000000..41cfc039ff8 --- /dev/null +++ b/java/ql/src/Metrics/Internal/ReftypeExtents.ql @@ -0,0 +1,12 @@ +/** + * @name Extents of reftypes + * @kind extent + * @metricType reftype + * @id java/reference-type-extents + */ +import java +import Extents + +from RangeRefType t +where t.fromSource() +select t.getLocation(), t diff --git a/java/ql/src/Metrics/Internal/ReftypeSourceLinks.ql b/java/ql/src/Metrics/Internal/ReftypeSourceLinks.ql new file mode 100644 index 00000000000..38db0d64679 --- /dev/null +++ b/java/ql/src/Metrics/Internal/ReftypeSourceLinks.ql @@ -0,0 +1,11 @@ +/** + * @name Source links of reference types + * @kind source-link + * @metricType reftype + * @id java/reference-type-source-links + */ +import java + +from RefType t +where t.fromSource() +select t, t.getFile() diff --git a/java/ql/src/Metrics/RefTypes/TAfferentCoupling.qhelp b/java/ql/src/Metrics/RefTypes/TAfferentCoupling.qhelp new file mode 100644 index 00000000000..b332c442a29 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TAfferentCoupling.qhelp @@ -0,0 +1,80 @@ + + + +

    +This metric measures the number of incoming dependencies for each reference +type, i.e. the number of other types that depend on it. +

    + +

    +Types that are depended upon by many other types typically require a lot of +effort to change, because changing them will force their dependents to change +as well. This is not necessarily a bad thing -- indeed, most systems will have +some such types (one example might be a string class). However, types with a high number +of incoming dependencies +and a high number of outgoing dependencies are hard to maintain. A type with both high afferent +coupling and high efferent coupling is referred to as a hub type. +Such types can be problematic, because on the one hand they are hard to +change (high afferent coupling), yet on the other they have many reasons to +change (high efferent coupling). This contradiction yields code that is very +hard to maintain or test. +

    + +

    +Conversely, some types may only be depended on by very few other types. Again, +this is not necessarily a problem -- we would expect, for example, that the +top-level types of a system would meet this criterion. When lower-level +types have very few incoming dependencies, however, it can be an indication +that a type is not pulling its weight. In extreme cases, types may even +have an afferent coupling of 0, indicating that they are dead +code. +

    + +
    + + +

    +It is unwise to refactor a type based purely on its high or low number of +incoming dependencies -- a type's afferent coupling value only makes sense +in the context of its role in the system as a whole. However, when combined +with other metrics such as efferent coupling, it is possible to make some +general recommendations: +

    + +
      +
    • +Types with high numbers of incoming and outgoing dependencies +are hub types that are prime candidates for refactoring (although this +will not always be easy). The general strategy is to split the type into +smaller types that each have fewer responsibilities, and refactor the code +that previously used the hub type accordingly. +
    • + +
    • +Types that have very few incoming dependencies and are not at the top level +of a system may not be pulling their weight and should be refactored, e.g. +using the 'Collapse Hierarchy' or 'Inline Class' techniques in [Fowler] +(see the section entitled 'Lazy Class' on p.68). +
    • + +
    • +Types that have an afferent coupling of 0 may be dead code -- +in this situation, they can often be deleted. +
    • +
    + + + +
    + + + +
  • +M. Fowler. Refactoring. Addison-Wesley, 1999. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/RefTypes/TAfferentCoupling.ql b/java/ql/src/Metrics/RefTypes/TAfferentCoupling.ql new file mode 100644 index 00000000000..408b3944c29 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TAfferentCoupling.ql @@ -0,0 +1,17 @@ +/** + * @name Incoming type dependencies + * @description The number of types that depend on a type. + * @kind treemap + * @treemap.warnOn highValues + * @metricType reftype + * @metricAggregate avg max + * @id java/incoming-type-dependencies + * @tags changeability + * modularity + */ +import java + +from RefType t +where t.fromSource() +select t, t.getMetrics().getAfferentCoupling() as n +order by n desc diff --git a/java/ql/src/Metrics/RefTypes/TEfferentCoupling.java b/java/ql/src/Metrics/RefTypes/TEfferentCoupling.java new file mode 100644 index 00000000000..b1c090acad8 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TEfferentCoupling.java @@ -0,0 +1,13 @@ +class X { + public void iUseY(Y y) { + y.doStuff(); + } + + public Y soDoY() { + return new Y(); + } + + public Z iUseZ(Z z1, Z z2) { + return z1.combine(z2); + } +} \ No newline at end of file diff --git a/java/ql/src/Metrics/RefTypes/TEfferentCoupling.qhelp b/java/ql/src/Metrics/RefTypes/TEfferentCoupling.qhelp new file mode 100644 index 00000000000..40e9e0ef5de --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TEfferentCoupling.qhelp @@ -0,0 +1,62 @@ + + + +

    +Efferent coupling is the number of outgoing dependencies for each class. In other words, it is the +number of other types on which each class depends. +

    + +

    +A class that depends on many other types is quite brittle, because if any of +its dependencies change, the class itself may have to change as well. Furthermore, the +reason for the high number of dependencies is often that different parts of +the class depend on different groups of other types, so it is common to +find that classes with high efferent coupling also lack cohesion. +

    + +
    + + +

    +You can reduce efferent coupling by splitting up a class so that each part depends on fewer types. +

    + +
    + + +

    In the following example, class X depends on both Y and +Z. +

    + + + +

    However, the methods that use Y do not use Z, and the methods +that use Z do not use Y. Therefore, the class can be split into +two classes, one of which depends only on Y and the other only on Z

    + + + +

    +Although this is a slightly artificial example, this sort of situation +does tend to occur in more complicated classes, +so the general technique is quite widely applicable. +

    + + + +
    + + + +
  • +IBM developerWorks: Evolutionary architecture and emergent design: Emergent design through metrics. +
  • +
  • +R. Martin, Agile Software Development: Principles, Patterns and Practices. Pearson, 2011. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/RefTypes/TEfferentCoupling.ql b/java/ql/src/Metrics/RefTypes/TEfferentCoupling.ql new file mode 100644 index 00000000000..e34e80cd12e --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TEfferentCoupling.ql @@ -0,0 +1,18 @@ +/** + * @name Outgoing type dependencies + * @description The number of types on which a class depends. + * @kind treemap + * @treemap.warnOn highValues + * @metricType reftype + * @metricAggregate avg max + * @id java/outgoing-type-dependencies + * @tags testability + * modularity + * maintainability + */ +import java + +from RefType t +where t.fromSource() +select t, t.getMetrics().getEfferentCoupling() as n +order by n desc diff --git a/java/ql/src/Metrics/RefTypes/TEfferentCouplingGood.java b/java/ql/src/Metrics/RefTypes/TEfferentCouplingGood.java new file mode 100644 index 00000000000..974760db57f --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TEfferentCouplingGood.java @@ -0,0 +1,15 @@ +class YX { + public void iUseY(Y y) { + y.doStuff(); + } + + public Y soDoY() { + return new Y(); + } +} + +class ZX { + public Z iUseZ(Z z1, Z z2) { + return z1.combine(z2); + } +} \ No newline at end of file diff --git a/java/ql/src/Metrics/RefTypes/TEfferentSourceCoupling.qhelp b/java/ql/src/Metrics/RefTypes/TEfferentSourceCoupling.qhelp new file mode 100644 index 00000000000..ceb74639c1e --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TEfferentSourceCoupling.qhelp @@ -0,0 +1,92 @@ + + + +

    +This metric measures the number of outgoing source dependencies for +each reference type, i.e. the number of other source types on which +each reference type depends. Dependencies into libraries are not +counted. +

    + +

    +Types that depend on many other types are quite brittle, because if any of +their dependencies change then they may have to as well. Furthermore, the +reason for the high number of dependencies is often that different bits of +the class depend on different sets of other types, so it is not uncommon to +find that classes with high efferent coupling also lack cohesion. +

    + +
    + + +

    +Efferent coupling can be reduced by splitting a class into pieces along its +dependency fault lines. For example, consider the following very simple class: +

    + + +class X { + public void iUseY(Y y) { + y.doStuff(); + } + + public Y soDoY() { + return new Y(); + } + + public Z iUseZ(Z z1, Z z2) { + return z1.combine(z2); + } +} + + +

    +In this class, X depends on both Y and +Z, but the functions that are using Y +are not also using Z, and vice-versa. We could thus +split the class into two, one of which depends only on Y +and the other only on Z: +

    + + +class YX { + public void iUseY(Y y) { + y.doStuff(); + } + + public Y soDoY() { + return new Y(); + } +} + +class ZX { + public Z iUseZ(Z z1, Z z2) { + return z1.combine(z2); + } +} + + +

    +Whilst this is a somewhat contrived example, this sort of situation +does tend to crop up even (or especially) in more complicated classes, +so the general technique is quite widely applicable. +

    + + + +
    + + + +
  • +A. Glover. Code quality for software architects. Published online, 2006. +
  • +
  • +R. Martin. Agile Software Development: Principles, Patterns and Practices. Pearson, 2011. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/RefTypes/TEfferentSourceCoupling.ql b/java/ql/src/Metrics/RefTypes/TEfferentSourceCoupling.ql new file mode 100644 index 00000000000..91cfcaaff9c --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TEfferentSourceCoupling.ql @@ -0,0 +1,18 @@ +/** + * @name Outgoing dependencies to source types + * @description The number of source types on which a type depends. + * @kind treemap + * @treemap.warnOn highValues + * @metricType reftype + * @metricAggregate avg max + * @id java/outgoing-source-type-dependencies + * @tags changeability + * maintainability + * modularity + */ +import java + +from RefType t +where t.fromSource() +select t, t.getMetrics().getEfferentSourceCoupling() as n +order by n desc diff --git a/java/ql/src/Metrics/RefTypes/TInheritanceDepth.qhelp b/java/ql/src/Metrics/RefTypes/TInheritanceDepth.qhelp new file mode 100644 index 00000000000..7d78490985b --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TInheritanceDepth.qhelp @@ -0,0 +1,103 @@ + + + +

    +This metric measures the inheritance depth of a reference type (e.g. a class). +

    + +

    +Whilst inheritance provides an avenue for code reuse, overly-deep class +hierarchies can become a maintenance headache. Classes that inherit from +many other classes can be brittle and hard to understand, because they +depend on all of the classes further up the hierarchy. Conversely, changes +to classes nearer the root of the hierarchy become harder and harder +to make without breaking the descendants. In extreme cases, where the +design of the hierarchy is seriously inappropriate, the class at the top of +the hierarchy can become a 'blob' class: a storage point for anything that +might be needed by one of its descendants. This is a key indicator that some +serious refactoring is needed. +

    + +
    + + +

    +As with many metrics, a high inheritance depth should be seen as an indicator +that something is amiss, but further investigation will be needed to clarify +the cause of the problem. Here are two possibilities: +

    + +
      + +
    • +A class and its superclass represent fundamentally the same abstraction. +In this case, they should generally be merged together (see the 'Collapse +Hierarchy' refactoring on pp.279-80 of [Fowler]). For example, suppose +that in the following class hierarchy both A and C represent fundamentally +the same thing, then they should be merged together as shown: + + + + + + + + + + +
      BeforeAfter
      BeforeAfter
      +
    • + +
    • +

      +The class hierarchy is trying to represent variation in more than one +dimension using single inheritance. This can lead to an unnecessarily +deep class hierarchy with lots of code duplication. For example, consider +the following: +

      + +Before + +

      +In languages that support it (such as C++), this situation could be modeled +somewhat more effectively using multiple inheritance, but an altogether better +approach is to use a component-based architecture (i.e. composition): +

      + +After + +

      +Using this method, each of the leaf classes in the above would be +represented as the composition of a type, fuel and style component, +e.g. a ColoredPetrolVan would have a Van type component, a Petrol +fuel component and a Colored style component. Note how this +effectively reduces both the height of the class hierarchy and the +amount of code duplication that will be necessary. +

      + +

      +For readers who are interested in this sort of approach, a good reference is +[West]. +

      +
    • + +
    + + + +
    + + + +
  • +M. Fowler. Refactoring. Addison-Wesley, 1999. +
  • +
  • +M. West. Evolve Your Hierarchy. Published online, 2007. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/RefTypes/TInheritanceDepth.ql b/java/ql/src/Metrics/RefTypes/TInheritanceDepth.ql new file mode 100644 index 00000000000..d1e8309c159 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TInheritanceDepth.ql @@ -0,0 +1,17 @@ +/** + * @name Type inheritance depth + * @description The depth of a reference type in the inheritance hierarchy. + * @kind treemap + * @treemap.warnOn highValues + * @metricType reftype + * @metricAggregate avg max + * @id java/inheritance-depth + * @tags changeability + * modularity + */ +import java + +from RefType t +where t.fromSource() +select t, t.getMetrics().getInheritanceDepth() as n +order by n desc diff --git a/java/ql/src/Metrics/RefTypes/TInheritanceDepth_MergeIntoSuperclassAfter.gv b/java/ql/src/Metrics/RefTypes/TInheritanceDepth_MergeIntoSuperclassAfter.gv new file mode 100644 index 00000000000..ff416c012f9 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TInheritanceDepth_MergeIntoSuperclassAfter.gv @@ -0,0 +1,9 @@ +graph +{ + node [shape=rectangle]; + + AC -- B; + AC -- D; + AC -- E; +} + diff --git a/java/ql/src/Metrics/RefTypes/TInheritanceDepth_MergeIntoSuperclassAfter.png b/java/ql/src/Metrics/RefTypes/TInheritanceDepth_MergeIntoSuperclassAfter.png new file mode 100644 index 0000000000000000000000000000000000000000..dc1b3b97703559c8d500d6a39114b274d3146cfb GIT binary patch literal 926 zcmV;P17ZA$P)%c00960{|C_rPXGV_0d!JMQvg8b*k%9#11m{H zK~#90?c2X@+b|FY@VrBYKwdo*@aO^s;x~x5BFGSJ{Wmy!hx#p2RSuLXiY*xCQHklWkd&bEZZH^0%{~mq%lSbg({&?B^0WJ zLX}Xc5(-s9p-L!J{a2LZoqlM(6ki`Fr`(qM;d9)~(hrxps;8$c3#FcS`iJe* zlzR1Jp^o>?F(Xk9>ujs`WeSvizQ0$eN9UN4C>QFmON6q~C#88B&y|(dtPkxAI-i*G ze5X{lQkOH;O#NcMUg&4;ECV~5=il62el=TVq{H7F{6FI4@far-H8rqOAw`#?B6gH1-Ql((3UtyW@O7C zl7c^~_k6id%->u2pE9>o^P-5!BrTctIU(-s!Rn#lh{Uy5~&_C&@rmqW1! zIu?)bA`ZnqtAm2?A_gTe4{i&C;+WG#f!l&pg5%J(;1t)4P72xkhWkH-%MT#(iVu)KPei;+g^VX zu!j(eKPbish~i%n5agc(pa40@5U&NG0J}NL9io=Py}vWi_&*fpng$v#6rl)3C_)j6 zP=q3sK9u(J--lUusBLOJ{{1Hoh57=Lr7Kz4%zrYb&dU1gJ7auZ&&Rl!|p=Badr`HC3!xsuQ;Xz=$H)$L-OciWE}=DTfku|_BX$Drb~G2DL) zG6Ir6G7Cj0LJ^8kgd!B7BzHe3krE13LZRyEH;Sk@i8OCPIsgCw07*qoM6N<$f;|4C AS^xk5 literal 0 HcmV?d00001 diff --git a/java/ql/src/Metrics/RefTypes/TInheritanceDepth_MergeIntoSuperclassBefore.gv b/java/ql/src/Metrics/RefTypes/TInheritanceDepth_MergeIntoSuperclassBefore.gv new file mode 100644 index 00000000000..28c3b4dd3e8 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TInheritanceDepth_MergeIntoSuperclassBefore.gv @@ -0,0 +1,10 @@ +graph +{ + node [shape=rectangle]; + + A -- B; + A -- C; + C -- D; + C -- E; +} + diff --git a/java/ql/src/Metrics/RefTypes/TInheritanceDepth_MergeIntoSuperclassBefore.png b/java/ql/src/Metrics/RefTypes/TInheritanceDepth_MergeIntoSuperclassBefore.png new file mode 100644 index 0000000000000000000000000000000000000000..50c337f06c5f31f01758a5a212d7e4e42eec75ad GIT binary patch literal 981 zcmV;`11kK9P)%c00960{|C_rPXGV_0d!JMQvg8b*k%9#17b-; zK~#90?b^R@(?Apl@cs`SP*^LF+L4G#?#z;GbO4bJB!+flC{p`h&=MhM!V0Fc|4JM- z?7R2#d=I@00sFah%ILYIBdDM@~KkvMC88F-dqlWsZ|hI_ad7PC7Y1@@sao zE;qT!U*$SCxj((gO&&kq=O*7&KXQ|=+v0g%vbt+m*Llg`-S>fQUUJ{5-}90$yKEn_ zlJ%?ZPw_b?d0Xxd-~TqyoW!Jfj+0J0>7ANvm<`Cl-oLYg7A50_4M=ISC<)XxAce_dBvjUb6ef$2 zU{wQBmMlWTMGZ(SL|xty*Je)?|s>wh4(s=d-bW7FD}2RNYOPGdWGX;aLn$*%Sk7l zbka#Dxn%zRSk2(BJ^?9cVx1n@07r8rX~`-{N#AT*l9a5D9DJL}Pm+?=k)poY#3UtI z6*&Y9NJ_FQQr0({nj|EvAw%U2NJ6q2QrI_}oWzq=kdfl(G8Lq>Z#F%NCMzdnr42|l zSve{0n;n6~k`^LMCf{es2qXs0Hf{ax4&5lGuF^EX~UNt$X>zf^mL^emcBf)-b^kL)d)28eKCeC-# zNhh6j(n%+$PImVh%a0r~^aC+-m5Xh)x?zx2xd}C~a!76$;-P;q@T(Y;Y_HCEWS}OO zT-@=<{#~|!{BRMU6CkSuWCQ$2IwZ?-nyWN8m-%0@?Du6mOoey#d3(jY`qa(n%+soB)}BKX#v{dR!!~g6IA;sywy+sCiRrNmy){7u-=rzMkGuR z#mholeTAjqN4F1L`7+{FZ+Qf}pSJCR;+kHU{57n@0jdCH!}A5hhB_6EtdB!@k_Jzb zg{!!mxD1=d-PBReuzg}(>EEvTN;Jy4rM|C9a}F5zrk?DNI?d!)dmj9I$?@S0T7C)I zrGYXS?Af07vXPnVR`=7 zzdyqFzQ39K1tgbz7hBf15VJo9@}a7!oU`w%zOLpgku^&@FW>4nXD6%k?62_AHF6D{ zlDm|AFqxk+qlrrciV;r3n9ZDTqrfd>qoGul}7& za>{Y!>Ye)H)(bz(%;c;^RPT(wxoGhEa-l{>d-Hi6LwSv^NB_3eiih_ho4EF z?|k&^ooKV_t1Bf2oyhyx?%Ep=n+f(^={xV2PD$JodR?PC{(Ons@}vI8L!7(;KKjBZ zhv6qS)|;Tgwj%o0m)CYp;3uA3>7Q5;r}iZ?9_Q6qD*u*)4xZM!MG=zOFXSGq`_;J2 z%L}q}$gqbCv)xZ=@$w4V^22j(D2&vgcRGC!R1l zDI*8=Vflk(zghdi$*YsK#U(#?Vh80EK0>;TzkEf18+^-n#dzae4l5TOAsBc*I%-e~ zhM$Bh(@($^;p%j&r$P3OvUIRxW=pCX=NZgObCLXbBogKvv?m4!Iu6SJO%=@&j#G>>0NfVOweX@XFNP1{b}3N@wsHW=wbr}Xt_ zeN?REyENwt^!=}&`^_8le6Y_knu~Dgl|@C!m`;zd-30P2Qi&(3r$NJU^g^6)pO)|d z@q|ujL-VJEVsOht*`yz5XJ{?wLhuKCmT#HGF+UlQue;wW7lKm+Hn8H8kAGQ5eb;=I zuz#bmd5%9G5en|AG$aEt*51` z52eTtnRVUEZT9;p<=uM$8Iu?`b`S@*K!eFFR!y%{33%N5Z1U3vfg3x?C$w}+XzZ%047sta z@iL>F#7NL-JInm{kp=lXiDc3jp~2y%yYKdYt|oIvjmI6#4snrQej}I7tD?%t9+6%} z+_Y8o=(i@XYL&DPX!Voo=Q^iJPnDp1zRT2zrdTa2d9ys{&R3SnHMFw|{}b4M{x!$k z4S+g7{Og_r5DfZGj}R;TCBXkkUWvcKlX%S)283-2SsQMv%j8bmjI)*t=-HWQgjeUR z{oN1=7%cq6#mY!48kE)@_&Gz7m923;bA_!VI)9VfFPvgks9b9JcY~9O2D+@~EGwe> z)iHP198ME?STSt^295qb_&1_Jz~lc%3S4|Zy1KfUWiKC>K^O?f9W)Oi6x}4DiO?!L zG?CAcjvL01NpaHA4GB13y^ZsNTncnC_pCA0HtRyVxj;h-}J!DF`6Ns@u=LtD$N77C8>OB*+thHc?P zLm|xFb^K=JN;8VKJ^hqbgqdRK5pJjJ&uyR$dK(+7%os`4Ls8+x)RjCW_~f;6SjFgzd>{$=(x#VIyxy_2^CmK-6YnEliy5LoPL}aY)uj3 zaor6w5c>GvN9OzI@svCTSG+5Y7(KPlyIs&7POo#am|dOk4An-^H*i}wpYYw=3XPnD zJLo~_2_2Eb5vI34g77zztDkOhLdPFDCO|&Klbv@bp#+VfgOn^`sAH;63!Mx zxlY61Ut1qa+%hF0yU)S~M4x!gbV?(dXvA#TCa$eq{tme11_>6PjT`iB3YuTsbd^q|r7x<}H} zB9Ht5pU>%kN*dN=vb$Zn>)8F{zC%nv0ahXi)du;41NWgXR>UKaF}fW3phBw^;s zk&b)yZn6(3e=da~0TzG+@|Y`-(r{{f`0;|Bz5DLIba!r+Jv3l1u-hwp55NXvV(z|+ zwb0_&v)AeX*h4SWS?=MDyNQX$L@QVvv}fDTD=I2f57^By!A5SpfP){@3m5$8}Tnt$lr$ zruJ#+gZ6W@Gub8Ml6apK6`S#@SpCOOHO7nmPdV3QZ)jG&O4P<-=N3mdIJuuNIjufD zZj_nP(VDP3Fq(`;wg&!$$2R?_G#W}^blyp%Y`uv$3fvXipjz)o6s__Q6~6P%iB zUL7HGL4V~{2uIhqQqFe7@G$1b$N1)RP^cdXoZhSwa){@y;8E#KrEKre9-X>#x0OJ1 z2qn#z(l$KiC(6=}l}U9k=p{WbcTLdM68&)LQT^FNK;Z0R7#@KJMVZ{D&Tp5BOu(su z3Tc7$c#^YptP*ECGh=e{W?GE8re$7Y8$8wO*yhCUP*dvXxQL1bzRBzNW*SUQb`u_+ z;cO@5Q_E1QxrTU88+p0-FJ=M#vFb1R1CwA=Qz8P)sX|0CLRIFaLp0c2AsccbeY3_& zc7C6Cxa#a31;Kh183xLb)+>e)bLcQUurc>>ke!B-7qGaiVtG-l!=89T=B4D}z}IiV>2hbQ-{fh;KjSYU2C<9Q4p(J( zLxD5WE%TB}z2PwS#X=!lTY5Ay9cq}BZ{{Xl;o=_C3IcKiQaqO}q67rh{{aX}Q}kNo zRpZ3U$w97@q3lzwV2QaGG27Cw{cm%zuj=iKfTpK)AIlgEA*Ls7530huThyA1s25&5 zj@d@OeA_?93C>s_sJR=!I8#2h4=PtNM8!TBDZaMO%Q4+#cgjM7{3<6u^piY*!ge0b zR=>3daLJj9l zc45UN2M~fUgNK-RtaR7tL9lPQ2D(2yvKZ&D8DJXmu!Nel_UT&l>4O3{a$>e&^xM{yS9H-`gb#swJTJs*_*(DRJ~Z|`ORm3g68PClxa0~xuCqi zfd_r~(j)kbqoS4j^bTpO%P^i7&0XWa9A&KuPwV!`zu+ei*m+SI-tAu*et>E{dlj(r zCV!nfUrpwvA^zvrw6tzZqMPBsLHF~!v89fcT(_5I9>r`E+O?1oDMyR_nIE31&mz&C zx>eU*qPgcxhd#+F`8taveq$MOP)OObE^6-GJybyKUN~OBDb+hWz~A4S@Qi2DZ~soeg1jUO_x6Olvr~+^_^VSu2 z0~`j1JeAYCxFdS?IUHV$orXr(UohFl`Cs#rVqeuS1XdXK4te&JiBu`1o@p6swKcna z4k*EM=_M6?K#n?d@BAsi;t}AMNS6GQ`9*eiPKyt7UQ||wA*yk>(f?gcj!~{>&5OGM zDQ=Vdc0R~HXe7#aPOOO~Cbp&kS1GNwsI>_s*hAixPsi;z!t2XY?S$0cfkaEsStnRu z0?RNIAB=q|2hD8;Sa&ZFL+pxjp-QqbsG5Z^)T!$(;6Y&lrsLri9u=s`Kj9Pk<_hY8 z7^;eHRt1kJEH>fmT$%ZPWuSnbHoUDqDHAe4%Csu<3mH9{YUX=7b$Tub$$LamR~|Ud zJnM%OF)@$Unk=%c3Q57&7Mot0rQEDPKKtN9Vz^FEH2W%H<<5#eNBhd+Iv7BNpL1!c zW^S_K{_@Jk{=F`+)ennQb09Hb^HG7Evw$z+jd$0y$`UMziD1yzcbC{ji+%brSFiwX z$U0L%ceb|=TvSMwaCDFr>P}F0pEcw9!)p!zXsDM4%Cdu$0cN|NQ`r#P5N{jf=%3j# zQ607xME3;lmG(+qjusv(TGdk(Dxo~?UQOMqk%BC?3+^VHu0nim-*YhY8av6~5!2;vJPhsNWLzJ!;Yyz% zobeEqvhsh#JkrzV^Gmuucdjc1OUTXt z2gYhaV$TB_F44Nu`43P1A@NdxVXa?QvKj!BVt~NUD~{E$1c42}uw|FWSRKfVD}W#< z7V^5m5(G+sAh0>%#cF{D%KIzL zvs|Z`gkc0_c0|JZ{fg*ys+k+GTXuvUueVro1=y#{`0l`cixEL0GQUNC_5k)1k?xlj5&UY z-1IVpT)D#-VK`03o%uPS6DS2wrrbAPm{Cn9IqGlP^efZCF75>OKC@@mHYV*z860zw zIGMg4vb>#~3DGHaAxRrJRWPDVhMSRYk?4L#=8B6!zxpK?*v?LunO1*tPCT^f&ehg$!by+*EJyW=RwQ zB}MD8%g^RNGI!kc!!;Kr+SM^^`Q3LMN!H(O-i;@*FRXGrqX(*(=D2K>gwO%6&x$SA z_PcgPS4WTeqC)5;UcRJ8Zv#{y-cDij^J>GB1(ICv=*>`?W{5!@d1X^~lKj3mI-@fX zX3W{84#VqvwG0@Kc)I+gwi!=-34T^J-CrAmr}g}%^saZ_K)zx8@Y1cvY>jGl`xEsy zw|uf`jjo4AGdrstL#MFyEz3J~*Z+MK?%6aJ(A2yK3Y zL0ch6QNP7WhY)rNx(0}FLZ&{WC6W@aNtM)OwCxsz&P>qPu}pO2&Khx*D?{wa6R{+6 z^p~>)!A=*oHTB!H>0S+Zuy;62x1P3K@l6!@k~qGqhx9viX!Oj;^o4xT8$SOMr@HI?_A%95k~i1=eRPg>&Ux6|yX|G$ zV-nTxcZrC?9jE;uNy4k}ZWL;p5v8ZTk@m?b`a?D?uw_kCpeW8TYyJ9IHnGzQ=UW^? zhadCEDz+_%uKv6BRz!8yF*hxcAuYBCD*i)R|!4lGBy*6)_qLn&08ap)g_9AMV4F~i~8 z>kySwD?^j3_85lcZ)=K;;hSAL1iMmb6d7WnsaT*nlD%KCph{qmeqvf*gHoG514DGz zdvl#XP*C|l!=S+Bydc%w{ES;JES4MQu%;`mN+WjtZ~mKkQT03j{ga8%y1lpTAYp)1 zh5fHxVs*Wa0Lf7gNJ#H}<;5$26lJU5vbxqPU}$%%$a!9FkECpm0Me!Xnl@{BFu<{7 zP46hy*Pj9$3g|I~z2zU&0FHz|PE6k8$jetuH0%%Cjk1;lT||1-K+P(P;!gscs66*J zkJYm`0^V#K)iGwx0L}rQJLhnXHMG153_a_O+d3}nMN@u4Cb#lQg(qt{8N~mXD=aWj zGpLKf!?6vGiF(1q*neL^uN?te*mvm{Bu4Y&afXnLwEF`ldw#5`F48wU)+K~xo?Afg z#BAsE{&2~^BVRo*Fjjp%FZTCP&H|#~?6LiVEcc UW9Bk2-)6HoXKhyf&+P~Q14|VpYybcN literal 0 HcmV?d00001 diff --git a/java/ql/src/Metrics/RefTypes/TInheritanceDepth_UseComponentsBefore.gv b/java/ql/src/Metrics/RefTypes/TInheritanceDepth_UseComponentsBefore.gv new file mode 100644 index 00000000000..be48287d742 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TInheritanceDepth_UseComponentsBefore.gv @@ -0,0 +1,17 @@ +graph +{ + node [shape=rectangle, fontsize=8]; + + Vehicle -- GasVehicle; + Vehicle -- DieselVehicle; + GasVehicle -- ColoredGasVehicle; + GasVehicle -- StripeyGasVehicle; + DieselVehicle -- ColoredDieselVehicle; + ColoredGasVehicle -- ColoredGasCar; + ColoredGasVehicle -- ColoredGasVan; + StripeyGasVehicle -- StripeyGasCar; + StripeyGasVehicle -- StripeyGasVan; + ColoredDieselVehicle -- ColoredDieselVan; + ColoredDieselVehicle -- ColoredDieselTruck; +} + diff --git a/java/ql/src/Metrics/RefTypes/TInheritanceDepth_UseComponentsBefore.png b/java/ql/src/Metrics/RefTypes/TInheritanceDepth_UseComponentsBefore.png new file mode 100644 index 0000000000000000000000000000000000000000..5d2e8b106b9a95d18ac6aa8009978cfa9b303d87 GIT binary patch literal 4056 zcmb_fc{J2*`ybnsk!4~ig)C8HS4NRXWXTf3jIXi843ddTX&HnH!`MZ39@*zRnvt<3 z#E7v)(@bRzAtWIs6+iW!^ZWaKp7WmbIrq7i&wZWiKKFgD>%L;`Z7qah(l7`FB5Zlu z)BytFr9dD&uKax47Kq)D`Syymw{bGt+}aWq6~*K6dU|?ta&qJ2;{^o;Mn*>A;o&VU zE$7aiW3$=Y+^wyxama4yw#0MI!PXISa^{UM1S0BYX=?0rGj}nsFL1bCvUB#}jJmHu zQnIZ<`3-ZJTk#W1ox{ZeWXZ2pnR5zn1+*@EX@(4%tw{7~*`DNmOqEA&%#a-K0#jRNmW^6rFQxu2~s~1~Z{nk``Ms*&=y>)49U)qIU7n4WK%nyIu2)S^# zWWR3ysbzw(LO>4c)fI3_Qrs%54M~QS450$br-1VhR@@WQ=Z0(kPQ?FOc1S)Ox8{8y zoLzh2luW07i})bH>iC(!Lu8SffFb)%I+4L6ffIoLbDWi;PyXVMUU3GZr+}%ybi3DD1D;RzkzC|811&eW zDoH<%k?r;aUHA~7JYyKSJ&9!`x-PUca)1RIsJZV;RPvv^+-aUx1oyrZ+o39SI|< z91|D%9vxV7_-q{6K_Qxga9Wz)i#tQAw+a8QEP2H?!YIH|IYT#gvU3Kj`9e%JiF4Yp z;e6QOxtBMk9pR%_wUwh#K3K0ahg^}O__io+&sApSZKW?a>oY?qR4#VcA2`xr`iFst zUlyN>G z@J*@BEx5fo?i%%DjYqyb=2n;V>eNzU2v>lip2m>|g|g(5W4CY|mDjs)4SP0QR}R z34%VRyor|SPN5?lV0SOff7@;gjVKO6Ya!3** zw4EJ*a^g$#D~8qF<-{Rzy7y(!On+tT%1AoO!OgYR-x@)eW)UT!&_Pp#pDblUju&PE zt_6AC0|-9ufZ1j6jKu3wdgCqx6RYy04cT_puMl>W%n=sDQ8TQ)5G00qq7$6JaC;&y zP2lSUp>%zjs{3MusN4z#F08>(IEkmZ9u>v8i^Qp?i*h~#6O;&5414Qs-S@4qkR*s@ zb_rI+8PvG4aPiKTJXb~pCp@PW;iJryexoCf6TX-wNQc(;-UIkteNDhf^&|4!){HDM zf@hBnNtgD|X)IB~7X#JXjJUWDGbes6#$C}rTTmw=G@L+Fu6t>!##MEel`Aj2(akYA zaXXO^9el*n_5!$KWk?GyCF34q=*r%2RcBho6*So6vKq`E!F5#H%9K|NEpTx;2Vgdk z_m{!BR3(8l+t+7mvTDico%3Gbw5|QF>FrPRF2s;jcf0q6td3=VZmA!+-j{I{#nK)9 zi!4y0*j?s*_^HG6a%BF<R#bZl48g#sX5mo_EOiFo$O+P!$cXDT(OP^~Dub8-|{gxR^K7UJ@> zmmYUJ7w6-K4KuWt70hdvVy#&;C?p)L^Br%}>crXvMWv5b-g?H(S}`f5N=35t@mb)= zgbu##K>_oljyt^#ORHEtJfqf$WKP@Pdau3Do$PEl?H{#vA%S7-fZ$0_>vf=Z9hr`@ zaW(RGH|l=`PkK-HRO=dil(N3;Ks~K8oV@(_Z_6r|07LIv@MHV>mAe&z*>=!AaJ%3UODDWI@Nw?swl!VJD@wq^n^+#2^=WDPJjQsdSI!zmcg+X`j_O--!;OtvT7z^8;PGh zzRlf2UIJaigkyr)y6Xum?IoWQf>qNmH}Eua4TS2(L+OEbxgvOBsaA1{?-tZLU|4Tm zJpWFf@J(Eln!UQGu;epQiU!#eFitw`)68r3xgCZrIJqs~f+G1m0LIf@QRvyGNR{Sj z!HFnz&R!)tz_o6HPI|45W&N7FO*ne7{wub{;JGHOvQYhFyM+l|H@;^WiCGu`6Wuc(^Ih zc+jTBIEl@H-w*AHP~^t-3p|4@ws4bFob^C-S?`ZXP8ww&W+3R?l#&x-=cZ3`Q7#Sh zRgE9>83GM#(!jaXFgr%1Ko54C03p~!p6EWOXpbm=!G5SB0uUM*Mu1^=1RposlZS?) zO~p6WFP#hlFOT0o8VvWX&wf7_n&FecxbFK=95dSw;HkpzlNgfwzEmtfbo*AQ&fPc` z62)QcdV>=%RM%b;D~9cvnd8b>gHV~3=UBOimS7lD zy>?l@+<1uA=6_T^yH&afwr6&3cP~u!LxqP%n#a*NPKgBy=+kHObD>qoVL^xZwzZ}o zU+Ys2d(F1p4gx%NuZ`{GLeQHZUglqsf6~oc9#77aolNy+=Pt4O&}O|C z7dEcyu93byn2X+l{E&xgK{tuc&uX2^%0WE5uwBKwadeLct7_C6^Vwr1S7dV@t3IDA zZX)Y)ljJO4GC0x%lZ>Z0^AqxuI#;F71uD73X9a}0y(l^sti;r#lT?XTW4$3!=?yO0 z*-%v@*{?ziTuaQ3;d!@>*4g0I7@_6nqU~4?`$`4C6BtG1jJxWM`w}g6>{XeF*s9YT zR&@o~JffqkV%xNdg|&2n4rB5^CEaMiPkJE)>9Nyx2^yc@i8twt_ba+U>wkqGvbb-@ za!|HHr$V3A>Z|0K=Bm$WI^%5KH0dp|GQ-n7sq~B`Z}Xt*yB&We_p!}}QZK$f_~(lT7tE>dthJ+k)qH6|TRml7G}tb{Hf4L}wMcwV>#_S3 zJznoZqxQ#sp)dcu*OYQ8Ykhsnun=^PV2Hfrz=@W;35DQb4 z*0v8*$9w7$=iT}3jRCQ4DL)ANwi9w)^TDW7!y#RBzSKj0O6tM@V(u<9^v#+=S;xvr}8Mx~exC|VX zew(?`q7GJToHI)N0#EWr%L)a3AxLy=r9ZMbsLiJSZXLUWyKq0(f5?j$Xs$0Cl>nke z!SFq?&hyC~sE2d@3-UeW*0+~YXDy}qx9^%b;Pvz4?CEBo$xds;<6VTj;5hUqk$5!c zA}$^AjS8}y)D`qkL#rX7i16I8RHd*IP^okTDe7#a&gvU>oI9yc)ZW9K^iw$JJbq#5 z`9@?p%l12Zzf9LvUZ2{%XYK}Tlcv4PKIV6MhM_f^v-yWV?oTf}U5yzX=~nV^)NXOq z?r8+@ebX!%>wcGOCzEPLTzp~S$CDFE$wbw@9HW4{S$;-U)i32&aw0j7QvN`HbnP>n z8aW_=s!v>o?GvZpVVZ&mwBf3!ZJ4m#|FzmXOJYQZ*M@JDg-KERN9`a8x9utB|FN|x Z$6u`km0Irg+#b3imS(o5RVJQy{twe+fKmVe literal 0 HcmV?d00001 diff --git a/java/ql/src/Metrics/RefTypes/TLackOfCohesionCK.qhelp b/java/ql/src/Metrics/RefTypes/TLackOfCohesionCK.qhelp new file mode 100644 index 00000000000..84fa9ebcd6c --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TLackOfCohesionCK.qhelp @@ -0,0 +1,69 @@ + + + +

    +A cohesive class is one in which most methods access the same fields. A class that +lacks cohesion is usually one that has multiple responsibilities. +

    + +

    +Various measures of lack of cohesion have been proposed. The Chidamber and Kemerer +version of lack of cohesion inspects pairs of methods. If there are many pairs that +access the same data, the class is cohesive. If there are many pairs that do not access +any common data, the class is not cohesive. More precisely, if:

    + +
      +
    • n1 is the number of pairs of distinct methods in a class that + do not have at least one commonly-accessed field, and
    • +
    • n2 is the number of pairs of distinct methods in a class that + do have at least one commonly-accessed field,
    • +
    + +

    the lack of cohesion measure (LCOM) can be defined as: +

    + +

    +LCOM = max((n1 - n2) / 2, 0) +

    + +

    +High values of LCOM indicate a significant lack of cohesion. As a rough +indication, an LCOM of 500 or more may give you cause for concern. +

    + +
    + + +

    +Classes generally lack cohesion because they have more responsibilities +than they should (see [Martin]). In general, the solution is to identify each +of the different responsibilities that the class has, and split them +into multiple classes, using the 'Extract Class' refactoring from [Fowler], for +example. +

    + + + +
    + + + +
  • +S. R. Chidamber and C. F. Kemerer, A metrics suite for object-oriented design. IEEE Transactions on Software Engineering, 20(6):476-493, 1994. +
  • +
  • +M. Fowler, Refactoring, pp. 65, 122-5. Addison-Wesley, 1999. +
  • +
  • +R. Martin. Agile Software Development: Principles, Patterns, and Practices +Chapter 8 - SRP: The Single-Responsibility Principle. Pearson Education, 2003. +
  • +
  • +O. de Moor et al, Keynote Address: .QL for Source Code Analysis. Proceedings of the 7th IEEE International Working Conference on Source Code Analysis and Manipulation, 2007. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/RefTypes/TLackOfCohesionCK.ql b/java/ql/src/Metrics/RefTypes/TLackOfCohesionCK.ql new file mode 100644 index 00000000000..33e1b973a19 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TLackOfCohesionCK.ql @@ -0,0 +1,17 @@ +/** + * @name Lack of type cohesion (CK) + * @description Lack of cohesion for a class as defined by Chidamber and Kemerer. + * @kind treemap + * @treemap.warnOn highValues + * @metricType reftype + * @metricAggregate avg max + * @id java/lack-of-cohesion-ck + * @tags modularity + * maintainability + */ +import java + +from RefType t +where t.fromSource() +select t, t.getMetrics().getLackOfCohesionCK() as n +order by n desc diff --git a/java/ql/src/Metrics/RefTypes/TLackOfCohesionHS.qhelp b/java/ql/src/Metrics/RefTypes/TLackOfCohesionHS.qhelp new file mode 100644 index 00000000000..4639d6b07e5 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TLackOfCohesionHS.qhelp @@ -0,0 +1,77 @@ + + + +

    +This metric provides an indication of the lack of cohesion of a reference +type, using a method proposed by Brian Henderson-Sellers in 1996. The idea +behind measuring a type's cohesion is that most methods in well-designed +classes will access the same fields. Types that exhibit a lack of cohesion +are often trying to take on multiple responsibilities, and should be split +into several smaller classes. +

    + +

    +Various measures of lack of cohesion have been proposed: while the basic +intuition is simple, the precise way to measure this property has been the +subject of intense debate. Rather than getting involved in this debate, +more than one such lack of cohesion measure is provided for comparison purposes. +

    + +

    +The Henderson-Sellers version of lack of cohesion can be defined as follows. +Let M be the set of methods in a class, F be the +set of fields in the class, r(f) be the number of methods that +access field f, and ar be the mean of +r(f) over f in F, then the lack of +cohesion measure LCOM can be defined as: +

    + +
    +LCOM = (ar - |M|) / (1 - |M|)
    +
    + +

    +As per [Walton], we restrict M to methods that read some field in +the class, and F to fields that are read by some method in the +class. High values of LCOM indicate a worrisome lack of cohesion. +The precise value of the metric for which warnings are issued is configurable, +but as a rough indication, an LCOM of 0.95 or more +may give you cause for concern. +

    + +
    + + +

    +Types generally lack cohesion because they are taking on more responsibilities +than they should (see [Martin] for more on responsibilities). In general, the +solution is to identify each of the different responsibilities the class is +taking on, and split them out into multiple classes, e.g. using the 'Extract +Class' refactoring from [Fowler]. +

    + + + +
    + + + +
  • +M. Fowler. Refactoring pp. 65, 122-5. Addison-Wesley, 1999. +
  • +
  • +B. Henderson-Sellers. Object-Oriented Metrics: Measures of Complexity. Prentice-Hall, 1996. +
  • +
  • +R. Martin. Agile Software Development: Principles, Patterns, and Practices +Chapter 8 - SRP: The Single-Responsibility Principle. Pearson Education, 2003. +
  • +
  • +O. de Moor et al. Keynote Address: .QL for Source Code Analysis. Proceedings of the 7th IEEE International Working Conference on Source Code Analysis and Manipulation, 2007. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/RefTypes/TLackOfCohesionHS.ql b/java/ql/src/Metrics/RefTypes/TLackOfCohesionHS.ql new file mode 100644 index 00000000000..667f100c410 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TLackOfCohesionHS.ql @@ -0,0 +1,16 @@ +/** + * @name Lack of type cohesion (HS) + * @description Lack of cohesion for a type as defined by Henderson-Sellers. + * @kind treemap + * @treemap.warnOn highValues + * @metricType reftype + * @metricAggregate avg max + * @id java/lack-of-cohesion-hs + * @tags modularity + */ +import java + +from RefType t +where t.fromSource() +select t, t.getMetrics().getLackOfCohesionHS() as n +order by n desc diff --git a/java/ql/src/Metrics/RefTypes/TLinesOfCode.qhelp b/java/ql/src/Metrics/RefTypes/TLinesOfCode.qhelp new file mode 100644 index 00000000000..a949a7c3201 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TLinesOfCode.qhelp @@ -0,0 +1,52 @@ + + + +

    +This metric measures the number of lines of code for each type. +

    + +

    +Large types can be problematic: +

    + +
      +
    • +They can be hard to understand and maintain, even with good tool support. +
    • + +
    • +They often arise as a result of bundling many unrelated things into the same +type, and so can be a symptom of weak type cohesion. +
    • +
    + +
    + + +

    +Types are generally too large because they are taking on more responsibilities +than they should (see [Martin] for more on responsibilities). In general, the +solution is to identify each of the different responsibilities the class is +taking on, and split them out into multiple classes, e.g. using the 'Extract +Class' refactoring from [Fowler]. +

    + + + +
    + + + +
  • +M. Fowler. Refactoring pp. 65, 122-5. Addison-Wesley, 1999. +
  • +
  • +R. Martin. Agile Software Development: Principles, Patterns, and Practices +Chapter 8 - SRP: The Single-Responsibility Principle. Pearson Education, 2003. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/RefTypes/TLinesOfCode.ql b/java/ql/src/Metrics/RefTypes/TLinesOfCode.ql new file mode 100644 index 00000000000..44205268778 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TLinesOfCode.ql @@ -0,0 +1,16 @@ +/** + * @name Lines of code in types + * @description The number of lines of code in a type. + * @kind treemap + * @treemap.warnOn highValues + * @metricType reftype + * @metricAggregate avg sum max + * @id java/lines-of-code-per-type + * @tags maintainability + */ +import java + +from RefType t +where t.fromSource() +select t, t.getMetrics().getNumberOfLinesOfCode() as n +order by n desc diff --git a/java/ql/src/Metrics/RefTypes/TLinesOfComment.qhelp b/java/ql/src/Metrics/RefTypes/TLinesOfComment.qhelp new file mode 100644 index 00000000000..329495a76dc --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TLinesOfComment.qhelp @@ -0,0 +1,55 @@ + + + +

    +This metric measures the number of comment lines for each type. +

    + +

    +Whilst the absolute number of comment lines in a type may not provide much +useful information out of context, a very small number of comments in a type +may indicate either a potentially worrying lack of documentation or that the +type was generated by an automated tool - a quick visual inspection should be +sufficient to distinguish between the two cases. +

    + +
    + + +

    +If the type is not an auto-generated one, it should be documented at the first +convenient opportunity (i.e. now). We note in passing that: +

    + +
      +
    • +From a pragmatic standpoint, it is clear that not all types are equally +important to document, so some common sense needs to be applied when deciding +which code should be documented first. +
    • + +
    • +Documenting entire types after the fact is not only onerous, but also often +yields lower-quality documentation than would have been written by the +original author at the time of writing the code (because other developers +may not understand the context as well as the person who wrote the code). +For this reason, finding completely undocumented types should be treated as +a sign that your documentation practices in general need to improve. +
    • +
    + + + +
    + + + +
  • +S. McConnell. Code Complete, 2nd Edition. Microsoft Press, 2004. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/RefTypes/TLinesOfComment.ql b/java/ql/src/Metrics/RefTypes/TLinesOfComment.ql new file mode 100644 index 00000000000..1bf1da4dc8d --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TLinesOfComment.ql @@ -0,0 +1,17 @@ +/** + * @name Lines of comment in types + * @description The number of lines of comment in a type. + * @kind treemap + * @treemap.warnOn lowValues + * @metricType reftype + * @metricAggregate avg sum max + * @id java/lines-of-comments-per-type + * @tags maintainability + * documentation + */ +import java + +from RefType t +where t.fromSource() +select t, t.getMetrics().getNumberOfCommentLines() as n +order by n desc diff --git a/java/ql/src/Metrics/RefTypes/TNumberOfCallables.qhelp b/java/ql/src/Metrics/RefTypes/TNumberOfCallables.qhelp new file mode 100644 index 00000000000..49827592849 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TNumberOfCallables.qhelp @@ -0,0 +1,102 @@ + + + +

    +This metric measures the number of methods in reference types below this +location in the tree. At a type level, it just measures the number of +methods in the type itself. +

    + +

    +A class with too many methods is generally trying to do too much, either at +the interface or implementation level or both. It is common for the set of +operations such a class provides to be diverse and wide-ranging, making it +difficult for readers to understand. Such +classes often lack cohesion and are prime candidates for refactoring. +

    + +
    + + +

    +Classes have too many methods for a variety of reasons, and the appropriate +fix is different in each case: +

    + +
      + +
    • +The class may be too large in general, and taking on more responsibilities +than it should (see [Martin] for more on responsibilities). In this case, +the solution is to identify each of the different responsibilities the class +is taking on, and split it into multiple classes, e.g. using the 'Extract +Class' refactoring from [Fowler]. +
    • + +
    • +There may be lots of duplication within the class itself, i.e. some of the +methods may be doing overlapping things. In this case, the solution is to +redesign the class so that each new method has a single, unique +responsibility. +
    • + +
    • +The class may be quite small at the implementation level, but trying to +provide its user with a wide range of 'utility' methods that don't actually +need to be part of the class. (A classic example of this is the +std::string class in the C++ Standard Library.) As [Sutter] +observes, there are at least two key problems with this approach: + +
        +
      • +It may be possible to generalize some of the utility methods beyond the +narrow context of the class in question -- by bundling them with the class, +the class author reduces the scope for functionality reuse. +
      • + +
      • +It's usually impossible for the class author to know every possible +operation that the user might want to perform on the class, so the public +interface will inherently be incomplete. New utility methods will end up +having a different syntax to the privileged public methods in the class, +negatively impacting on code consistency. +
      • +
      + +To refactor a class like this, simply move its utility methods elsewhere, +paring its public interface down to the bare minimum. +
    • + +
    • +The class may be a base class in a badly-designed inheritance hierarchy. +Such classes have many public methods in order to support features that +are only used by one or two of their descendants. In this situation, the +solution is to redesign the hierarchy, possibly by refactoring it into a +component-based architecture (see [Microsoft Patterns & Practices Team]). +
    • + +
    + +
    + + + +
  • +M. Fowler. Refactoring pp. 65, 122-5. Addison-Wesley, 1999. +
  • +
  • +R. Martin. Agile Software Development: Principles, Patterns, and Practices +Chapter 8 - SRP: The Single-Responsibility Principle. Pearson Education, 2003. +
  • +
  • +H. Sutter. GotW #84: Monoliths "Unstrung". Published online, 2002. +
  • +
  • +Microsoft Patterns & Practices Team. Architectural Patterns and Styles Microsoft Application Architecture Guide, 2nd Edition. Microsoft Press, 2009. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/RefTypes/TNumberOfCallables.ql b/java/ql/src/Metrics/RefTypes/TNumberOfCallables.ql new file mode 100644 index 00000000000..87500cd8d6f --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TNumberOfCallables.ql @@ -0,0 +1,16 @@ +/** + * @name Number of methods + * @description The number of methods and constructors in a reference type. + * @kind treemap + * @treemap.warnOn highValues + * @metricType reftype + * @metricAggregate avg sum max + * @id java/functions-per-type + * @tags maintainability + */ +import java + +from RefType t +where t.fromSource() +select t, t.getMetrics().getNumberOfCallables() as n +order by n desc diff --git a/java/ql/src/Metrics/RefTypes/TNumberOfFields.java b/java/ql/src/Metrics/RefTypes/TNumberOfFields.java new file mode 100644 index 00000000000..584a29f73cf --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TNumberOfFields.java @@ -0,0 +1,10 @@ +class Person { + private String m_firstName; + private String m_LastName; + private int m_houseNumber; + private String m_street; + private String m_settlement; + private Country m_country; + private Postcode m_postcode; + // ... +} \ No newline at end of file diff --git a/java/ql/src/Metrics/RefTypes/TNumberOfFields.qhelp b/java/ql/src/Metrics/RefTypes/TNumberOfFields.qhelp new file mode 100644 index 00000000000..befc6409449 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TNumberOfFields.qhelp @@ -0,0 +1,59 @@ + + + +

    +A class that contains a high number of fields may indicate the following problems: +

    + +
      +
    • The class may be too big or have too many responsibilities.
    • +
    • Several of the fields may be part of the same abstraction.
    • +
    + +
    + + +

    +The solution depends on the reason for the high number of fields: +

    + +
      +
    • +If the class is too big, you should split it into multiple smaller classes. +
    • + +
    • +

      +If several of the fields are part of the same abstraction, you should +group them into a separate class, using the 'Extract Class' refactoring described +in [Fowler]. +

      +
    • +
    + +
    + + +

    In the following example, class Person contains a number of fields.

    + + + +

    +This can be refactored by grouping fields that are part of the same abstraction into new classes +Name and Address.

    + + + +
    + + + +
  • +M. Fowler, Refactoring. Addison-Wesley, 1999. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/RefTypes/TNumberOfFields.ql b/java/ql/src/Metrics/RefTypes/TNumberOfFields.ql new file mode 100644 index 00000000000..50dd6c54511 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TNumberOfFields.ql @@ -0,0 +1,17 @@ +/** + * @name Number of fields + * @description The number of fields in a class, excluding enum constants. + * @kind treemap + * @treemap.warnOn highValues + * @metricType reftype + * @metricAggregate avg sum max + * @id java/fields-per-type + * @tags maintainability + * complexity + */ +import java + +from RefType t +where t.fromSource() +select t, t.getMetrics().getNumberOfExplicitFields() as n +order by n desc diff --git a/java/ql/src/Metrics/RefTypes/TNumberOfFieldsGood.java b/java/ql/src/Metrics/RefTypes/TNumberOfFieldsGood.java new file mode 100644 index 00000000000..dda74f185b6 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TNumberOfFieldsGood.java @@ -0,0 +1,20 @@ +class Name { + private String m_firstName; + private String m_lastName; + // ... +} + +class Address { + private int m_houseNumber; + private String m_street; + private String m_settlement; + private Country m_country; + private Postcode m_postcode; + // ... +} + +class Person { + private Name m_name; + private Address m_address; + // ... +} \ No newline at end of file diff --git a/java/ql/src/Metrics/RefTypes/TNumberOfStatements.qhelp b/java/ql/src/Metrics/RefTypes/TNumberOfStatements.qhelp new file mode 100644 index 00000000000..076f1532aef --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TNumberOfStatements.qhelp @@ -0,0 +1,63 @@ + + + +

    +This metric measures the number of statements that occur in a type. +

    + +

    +If there are too many statements in a type, it is generally +for one of two reasons: +

    + +
      +
    • +One or more individual methods of the type contain too many statements, making +them hard to understand, difficult to check and a common source of defects +(particularly towards the end of the methods, since few people ever read that +far). These methods typically lack cohesion because they are trying to do too many things. +
    • + +
    • +The type contains too many methods, which generally indicates that it is +trying to do too much, either at the interface or implementation level or +both. It can be difficult for readers to understand because there is a +confusing list of operations. +
    • +
    + +
    + + +

    +As described above, types reported as violations by this rule contain one +or more methods with too many statements, or the type itself contains +too many methods.

    + +
      +
    • +Individual methods of the type that contain too many statements should be refactored into multiple, smaller methods. As a rough +guide, methods should be able to fit on a single screen or side of A4. Anything +longer than that increases the risk of introducing new defects during routine code changes. +
    • + +
    • +Types that contain too many methods often lack cohesion and are +prime candidates for refactoring. +
    • +
    + + +
    + + + +
  • +M. Fowler. Refactoring. Addison-Wesley, 1999. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/RefTypes/TNumberOfStatements.ql b/java/ql/src/Metrics/RefTypes/TNumberOfStatements.ql new file mode 100644 index 00000000000..fb46b85a6d8 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TNumberOfStatements.ql @@ -0,0 +1,17 @@ +/** + * @name Number of statements in types + * @description The number of statements in the methods and constructors of a type. + * @kind treemap + * @treemap.warnOn highValues + * @metricType reftype + * @metricAggregate avg sum max + * @id java/statements-per-type + * @tags maintainability + */ +import java + +from RefType t, int n +where t.fromSource() and + n = count(Stmt s | s.getEnclosingCallable() = t.getACallable()) +select t, n +order by n desc diff --git a/java/ql/src/Metrics/RefTypes/TPercentageOfComments.qhelp b/java/ql/src/Metrics/RefTypes/TPercentageOfComments.qhelp new file mode 100644 index 00000000000..81fe1dfcf6f --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TPercentageOfComments.qhelp @@ -0,0 +1,55 @@ + + + +

    +This metric measures the percentage of a type's lines that are comment rather +than code. +

    + +

    +A very low percentage of comments in a type may indicate either a potentially +worrying lack of documentation or that the type was generated by an automated +tool - a quick visual inspection should be sufficient to distinguish between +the two cases. +

    + +
    + + +

    +If the type is not an auto-generated one, it should be documented at the first +convenient opportunity (i.e. now). We note in passing that: +

    + +
      +
    • +From a pragmatic standpoint, it is clear that not all types are equally +important to document, so some common sense needs to be applied when deciding +which code should be documented first. +
    • + +
    • +Documenting entire types after the fact is not only onerous, but also often +yields lower-quality documentation than would have been written by the +original author at the time of writing the code (because other developers +may not understand the context as well as the person who wrote the code). +For this reason, finding completely undocumented types should be treated as +a sign that your documentation practices in general need to improve. +
    • +
    + + + +
    + + + +
  • +S. McConnell. Code Complete, 2nd Edition. Microsoft Press, 2004. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/RefTypes/TPercentageOfComments.ql b/java/ql/src/Metrics/RefTypes/TPercentageOfComments.ql new file mode 100644 index 00000000000..e18f56e0412 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TPercentageOfComments.ql @@ -0,0 +1,19 @@ +/** + * @name Percentage of documentation in types + * @description The percentage of a type's lines that are comment rather than code. + * @kind treemap + * @treemap.warnOn lowValues + * @metricType reftype + * @metricAggregate avg max + * @id java/documentation-ratio-per-type + * @tags maintainability + * documentation + */ +import java + +from RefType t, int n +where t.fromSource() + and n = (100 * t.getMetrics().getNumberOfCommentLines()) / + (t.getMetrics().getNumberOfCommentLines() + t.getMetrics().getNumberOfLinesOfCode()) +select t, n +order by n desc diff --git a/java/ql/src/Metrics/RefTypes/TPercentageOfComplexCode.qhelp b/java/ql/src/Metrics/RefTypes/TPercentageOfComplexCode.qhelp new file mode 100644 index 00000000000..c36c53ba7ec --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TPercentageOfComplexCode.qhelp @@ -0,0 +1,43 @@ + + + +

    +This metric measures the percentage of the code within each type that is part +of a complex method (defined to be a method that has a high cyclomatic +complexity, i.e. there are a high number of linearly-independent execution +paths through the method). +

    + +

    +Methods with high cyclomatic complexity are typically difficult to understand +and test. Types whose code is primarily contained within such tricky methods +are often strong candidates for refactoring. +

    + +
    + + +

    +Each of the individual methods whose cyclomatic complexity is too high should +be simplified, e.g. by tidying up complex logic and/or by splitting the method +into multiple smaller methods using the 'Extract Method' refactoring from +[Fowler]. If splitting the methods up results in a class with too many +methods, the refactoring should be followed up with another one to resolve the +new problem (as per the advice given for that situation). +

    + + + +
    + + + +
  • +M. Fowler. Refactoring. Addison-Wesley, 1999. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/RefTypes/TPercentageOfComplexCode.ql b/java/ql/src/Metrics/RefTypes/TPercentageOfComplexCode.ql new file mode 100644 index 00000000000..0a8d5fe392d --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TPercentageOfComplexCode.ql @@ -0,0 +1,28 @@ +/** + * @name Percentage of complex code in types + * @description The percentage of a type's code that is part of a complex method. + * @kind treemap + * @treemap.warnOn highValues + * @metricType reftype + * @metricAggregate avg max + * @id java/complex-code-ratio-per-type + * @tags testability + * complexity + */ +import java + +pragma[noopt] +int complexCallableLines(MetricCallable c, RefType owner) { + c.getDeclaringType() = owner and + exists(int cc | c.getCyclomaticComplexity() = cc and cc > 18) and + result = c.getNumberOfLinesOfCode() +} + +from MetricRefType t, int ccLoc, int loc +where t.fromSource() and + not (t instanceof GeneratedClass) and + ccLoc = sum(Callable c, int cLoc | cLoc = complexCallableLines(c, t) | cLoc) and + loc = t.getNumberOfLinesOfCode() and + loc != 0 +select t, ((float)ccLoc*100)/loc as n +order by n desc diff --git a/java/ql/src/Metrics/RefTypes/TResponse.qhelp b/java/ql/src/Metrics/RefTypes/TResponse.qhelp new file mode 100644 index 00000000000..9f1ff70208d --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TResponse.qhelp @@ -0,0 +1,41 @@ + + + +

    +Response is the number of unique methods (or constructors) that can be called by all the +methods (or constructors) of a class. For example, if a class has two methods (X and Y), and one method calls +methods A and B, and the other method calls methods A and C, the class's response is 3 (methods A, B, +and C are called). +

    + +

    +Classes that have a high response can be difficult to understand and test. This is +because you have to read through all the methods that can possibly be called +to fully understand the class. +

    + +
    + + +

    +Generally, when a class has a high response, it is because it +contains methods that individually make large numbers of calls or +because it has high efferent coupling. The solution is therefore to fix these +underlying problems, and the class's response decreases accordingly. +

    + + + +
    + + + +
  • +S. R. Chidamber and C. F. Kemerer, A metrics suite for object-oriented design. IEEE Transactions on Software Engineering, 20(6):476-493, 1994. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/RefTypes/TResponse.ql b/java/ql/src/Metrics/RefTypes/TResponse.ql new file mode 100644 index 00000000000..b9bf128194d --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TResponse.ql @@ -0,0 +1,18 @@ +/** + * @name Class response + * @description The number of unique methods or constructors that can be called by all the methods + * or constructors of a class. + * @kind treemap + * @treemap.warnOn highValues + * @metricType reftype + * @metricAggregate avg max + * @id java/response-per-type + * @tags maintainability + * complexity + */ +import java + +from RefType t +where t.fromSource() +select t, t.getMetrics().getResponse() as n +order by n desc diff --git a/java/ql/src/Metrics/RefTypes/TSelfContainedness.qhelp b/java/ql/src/Metrics/RefTypes/TSelfContainedness.qhelp new file mode 100644 index 00000000000..3a4d2bc930e --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TSelfContainedness.qhelp @@ -0,0 +1,71 @@ + + + +

    +This metric measures the percentage of the types on which a type depends for +which we have source code available. +

    + +

    +The availability of source code is one of the key factors affecting how easy +or difficult it will be to build a software project in the future, especially +on platforms other than those for which it was originally developed. Projects +will a high level of self-containedness are likely to be more portable and +easier to build in ten years' time than those that depend on many binary-only, +third-party libraries. (This is one reason why many of the dependencies of +open-source projects are distributed as source code, aside from the fact that +the binaries are generally larger and more unwieldy to distribute.) +

    + +

    +In the context of Java's platform independence, the availability of source code +is less critical than it is for platform-dependent languages. +However, note that there can be minor binary incompatibilities between +different versions of Java. +

    + +
    + + +

    +Low self-containedness may or may not be a problem, depending on the context +of your project. However, if you determine that it is an issue for you, it is +best tackled at a project level, in the following ways: +

    + +
      +
    • +Try to use libraries for which the source code is available. +
    • +
    • +Try to obtain the source code for binary-only libraries from the authors. +
    • +
    • +Where practical, rewrite parts of your code to reduce your dependence on +external libraries. +
    • +
    + + + +
    + + + +
  • +Wikipedia. Software portability. Published online. +
  • +
  • + Java SE 6 Compatibility, + Oracle Technology Network. +
  • +
  • + The Java Language Specification + Chapter 13 +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/RefTypes/TSelfContainedness.ql b/java/ql/src/Metrics/RefTypes/TSelfContainedness.ql new file mode 100644 index 00000000000..672177aa345 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TSelfContainedness.ql @@ -0,0 +1,17 @@ +/** + * @name Self-containedness of types + * @description The percentage of the types on which a type depends for which we have the source code. + * @kind treemap + * @treemap.warnOn lowValues + * @metricType reftype + * @metricAggregate avg max + * @id java/source-dependency-ratio-per-type + * @tags portability + * modularity + */ +import java + +from RefType t, float n +where t.fromSource() + and n = 100 * t.getMetrics().getEfferentSourceCoupling() / t.getMetrics().getEfferentCoupling() +select t, n diff --git a/java/ql/src/Metrics/RefTypes/TSizeOfAPI.qhelp b/java/ql/src/Metrics/RefTypes/TSizeOfAPI.qhelp new file mode 100644 index 00000000000..3095d82049a --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TSizeOfAPI.qhelp @@ -0,0 +1,98 @@ + + + +

    +This metric measures the number of public methods for each public class. +

    + +

    +A class with too many public methods is generally trying to do too much, +either at the interface or implementation level or both. +It can be difficult for readers to understand because there is a +confusing list of operations. Such classes often lack cohesion and are prime candidates for refactoring. +

    + +
    + + +

    +Classes have wide interfaces for a variety of different reasons, and the +appropriate fix is different in each case: +

    + +
      + +
    • +The class may be too large in general, and taking on more responsibilities +than it should (see [Martin] for more on responsibilities). In this case, +the solution is to identify each of the different responsibilities the class +is taking on, and split it into multiple classes, e.g. using the 'Extract +Class' refactoring from [Fowler]. +
    • + +
    • +There may be lots of duplication within the class itself, i.e. some of the +public methods may be doing overlapping things. In this case, the solution +is to redesign the interface so that each new public method has a single, +unique responsibility. +
    • + +
    • +The class may be quite small at the implementation level, but trying to +provide its user with a wide range of 'utility' methods that don't actually +need to be part of the class. (A classic example of this is the +std::string class in the C++ Standard Library.) As [Sutter] +observes, there are at least two key problems with this approach: + +
        +
      • +It may be possible to generalize some of the utility methods beyond the +narrow context of the class in question -- by bundling them with the class, +the class author reduces the scope for functionality reuse. +
      • + +
      • +It's usually impossible for the class author to know every possible +operation that the user might want to perform on the class, so the public +interface will inherently be incomplete. New utility methods will end up +having a different syntax to the privileged public methods in the class, +negatively impacting on code consistency. +
      • +
      + +To refactor a class like this, simply move its utility methods elsewhere, +paring its public interface down to the bare minimum. +
    • + +
    • +The class may be a base class in a badly-designed inheritance hierarchy. +Such classes have many public methods in order to support features that +are only used by one or two of their descendants. In this situation, the +solution is to redesign the hierarchy, possibly by refactoring it into a +component-based architecture. +
    • + +
    + + + +
    + + + +
  • +M. Fowler. Refactoring pp. 65, 122-5. Addison-Wesley, 1999. +
  • +
  • +R. Martin. Agile Software Development: Principles, Patterns, and Practices +Chapter 8 - SRP: The Single-Responsibility Principle. Pearson Education, 2003. +
  • +
  • +H. Sutter. GotW #84: Monoliths "Unstrung". Published online, 2002. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/RefTypes/TSizeOfAPI.ql b/java/ql/src/Metrics/RefTypes/TSizeOfAPI.ql new file mode 100644 index 00000000000..fbc9130cbf1 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TSizeOfAPI.ql @@ -0,0 +1,19 @@ +/** + * @name Size of a type's API + * @description The number of public methods in a public class. + * @kind treemap + * @treemap.warnOn highValues + * @metricType reftype + * @metricAggregate avg sum max + * @id java/public-functions-per-type + * @tags testability + * modularity + */ +import java + +from Class c, int n +where c.fromSource() and + c.isPublic() and + n = count(Method m | c.getAMethod() = m and m.isPublic()) +select c, n +order by n desc diff --git a/java/ql/src/Metrics/RefTypes/TSpecialisationIndex.java b/java/ql/src/Metrics/RefTypes/TSpecialisationIndex.java new file mode 100644 index 00000000000..15e6c76f929 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TSpecialisationIndex.java @@ -0,0 +1,40 @@ +abstract class Animal { + protected String animalName; + + public Animal(String name) { + animalName = name; + } + + public String getName(){ + return animalName; + } + public abstract String getKind(); +} + +class Dog extends Animal { + public Dog(String name) { + super(name); + } + + public String getName() { // This method duplicates the method in class 'Cat'. + return animalName + " the " + getKind(); + } + + public String getKind() { + return "dog"; + } + } + +class Cat extends Animal { + public Cat(String name) { + super(name); + } + + public String getName() { // This method duplicates the method in class 'Dog'. + return animalName + " the " + getKind(); + } + + public String getKind() { + return "cat"; + } +} diff --git a/java/ql/src/Metrics/RefTypes/TSpecialisationIndex.qhelp b/java/ql/src/Metrics/RefTypes/TSpecialisationIndex.qhelp new file mode 100644 index 00000000000..15bd63ebc14 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TSpecialisationIndex.qhelp @@ -0,0 +1,67 @@ + + + +

    +Specialization index is the extent to which a subclass overrides the +behavior of its ancestor classes. It is computed as follows:

    + +
      +
    1. Determine the number of overridden methods in the subclass (not counting overrides of + abstract methods).
    2. +
    3. Multiply this number by the subclass's depth in the inheritance hierarchy.
    4. +
    5. Divide the result by the subclass's total number of methods.
    6. +
    + +

    +If a class overrides many of the methods of its ancestor classes, it +indicates that the abstractions in the ancestor classes should be reviewed. +This is particularly true for subclasses that are lower down in the +inheritance hierarchy. In general, subclasses should add behavior to their +superclasses, rather than redefining the behavior that is already there. +

    + +
    + + +

    +The most common reason that classes have a high specialization index is that +multiple subclasses specialize a common base class in the same +way. In this case, the relevant method(s) should be pulled up into the base +class (see the 'Pull Up Method' refactoring in [Fowler]).

    + +
    + + +

    In the following example, duplicating getName in each of the subclasses +is unnecessary. +

    + + + +

    +To decrease the specialization index of the subclasses, pull up getName into the base class. +

    + + + + + +
    + + + +
  • +M. Fowler, Refactoring, pp. 260-3. Addison-Wesley, 1999. +
  • +
  • +M. Lorenz and J. Kidd, Object-oriented Software Metrics. Prentice Hall, 1994. +
  • +
  • +O. de Moor et al, Keynote Address: .QL for Source Code Analysis. Proceedings of the 7th IEEE International Working Conference on Source Code Analysis and Manipulation, 2007. +
  • + + +
    +
    diff --git a/java/ql/src/Metrics/RefTypes/TSpecialisationIndex.ql b/java/ql/src/Metrics/RefTypes/TSpecialisationIndex.ql new file mode 100644 index 00000000000..f5c6916d492 --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TSpecialisationIndex.ql @@ -0,0 +1,19 @@ +/** + * @name Type specialization index + * @description The extent to which a subclass overrides the behavior of its superclasses. + * @kind treemap + * @treemap.warnOn highValues + * @metricType reftype + * @metricAggregate avg max + * @id java/type-specialization-index + * @tags modularity + * maintainability + */ +import java + +from RefType t +where t.fromSource() and + (t instanceof ParameterizedType implies t instanceof GenericType) and + not t instanceof AnonymousClass +select t, t.getMetrics().getSpecialisationIndex() as n +order by n desc diff --git a/java/ql/src/Metrics/RefTypes/TSpecialisationIndexGood.java b/java/ql/src/Metrics/RefTypes/TSpecialisationIndexGood.java new file mode 100644 index 00000000000..443c4e054fa --- /dev/null +++ b/java/ql/src/Metrics/RefTypes/TSpecialisationIndexGood.java @@ -0,0 +1,33 @@ +abstract class Animal { + private String animalName; + + public Animal(String name) { + animalName = name; + } + + public String getName() { // Method has been pulled up into the base class. + return animalName + " the " + getKind(); + } + + public abstract String getKind(); +} + +class Dog extends Animal { + public Dog(String name) { + super(name); + } + + public String getKind() { + return "dog"; + } +} + +class Cat extends Animal { + public Cat(String name) { + super(name); + } + + public String getKind() { + return "cat"; + } +} diff --git a/java/ql/src/Metrics/queries.xml b/java/ql/src/Metrics/queries.xml new file mode 100644 index 00000000000..0d33187fe86 --- /dev/null +++ b/java/ql/src/Metrics/queries.xml @@ -0,0 +1 @@ + diff --git a/java/ql/src/Performance/ConcatenationInLoops.java b/java/ql/src/Performance/ConcatenationInLoops.java new file mode 100644 index 00000000000..28e0b4f3154 --- /dev/null +++ b/java/ql/src/Performance/ConcatenationInLoops.java @@ -0,0 +1,18 @@ +public class ConcatenationInLoops { + public static void main(String[] args) { + Random r = new Random(123); + long start = System.currentTimeMillis(); + String s = ""; + for (int i = 0; i < 65536; i++) + s += r.nextInt(2); + System.out.println(System.currentTimeMillis() - start); // This prints roughly 4500. + + r = new Random(123); + start = System.currentTimeMillis(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 65536; i++) + sb.append(r.nextInt(2)); + s = sb.toString(); + System.out.println(System.currentTimeMillis() - start); // This prints 5. + } +} \ No newline at end of file diff --git a/java/ql/src/Performance/ConcatenationInLoops.qhelp b/java/ql/src/Performance/ConcatenationInLoops.qhelp new file mode 100644 index 00000000000..cc5f79a3741 --- /dev/null +++ b/java/ql/src/Performance/ConcatenationInLoops.qhelp @@ -0,0 +1,57 @@ + + + + + +

    When string concatenation is performed using the "+" operator, the compiler +translates this operation to a suitable manipulation, possibly constructing +several intermediate strings. In general, because strings are immutable, at least +one new string has to be constructed to hold the result.

    + +

    Building up a string one piece at a time in a loop requires a new string on every iteration, +repeatedly copying longer and longer prefixes to fresh string +objects. As a result, performance can be severely degraded.

    + +
    + +

    Whenever a string is constructed using a loop that iterates more than just a +few times, it is usually better to create a StringBuffer or +StringBuilder object and append to that. Because such buffers are based on +mutable character arrays, which do not require a new string to be created for each concatenation, +they can reduce the cost of repeatedly growing the string.

    + +

    To choose between StringBuffer and StringBuilder, +check if the new buffer object can possibly be accessed by several different threads +while in use. If multi-thread safety is required, use a StringBuffer. For +purely local string buffers, you can avoid the overhead of synchronization by using a +StringBuilder.

    + +
    + + +

    The following example shows a simple test that measures the time taken to construct a string. +It constructs the same string of 65,536 binary digits, character-by-character, first by repeatedly +appending to a string, and then by using a StringBuilder. The second method is three +orders of magnitude faster.

    + + + +
    + + +
  • + J. Bloch, Effective Java (second edition), + Item 51. + Addison-Wesley, 2008. +
  • +
  • + Java Platform, Standard Edition 6, API Specification: + StringBuffer, + StringBuilder. +
  • + + +
    +
    diff --git a/java/ql/src/Performance/ConcatenationInLoops.ql b/java/ql/src/Performance/ConcatenationInLoops.ql new file mode 100644 index 00000000000..a1151892a14 --- /dev/null +++ b/java/ql/src/Performance/ConcatenationInLoops.ql @@ -0,0 +1,69 @@ +/** + * @name String concatenation in loop + * @description Performing string concatenation in a loop that iterates many times may affect + * performance. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/string-concatenation-in-loop + * @tags efficiency + * maintainability + */ +import semmle.code.java.Type +import semmle.code.java.Expr +import semmle.code.java.Statement +import semmle.code.java.JDK + +/** A use of `+` that has type `String`. */ +class StringCat extends AddExpr { + StringCat() { + this.getType() instanceof TypeString + } +} + +/** + * An assignment of the form + * + * ``` + * v = ... + ... v ... + * ``` + * or + * + * ``` + * v += ... + * ``` + * where `v` is a simple variable (and not, for example, + * an array element). + */ +predicate useAndDef(Assignment a, Variable v) { + a.getDest() = v.getAnAccess() and + v.getType() instanceof TypeString and + ( + a instanceof AssignAddExpr + or + ( + exists(VarAccess use | use.getVariable() = v | + use.getParent*() = a.getSource() + ) and + a.getSource() instanceof AddExpr + ) + ) +} + +predicate declaredInLoop(LocalVariableDecl v, LoopStmt loop) { + exists(LocalVariableDeclExpr e | + e.getVariable() = v and + e.getEnclosingStmt().getParent*() = loop.getBody() + ) or + exists(EnhancedForStmt for | for = loop | + for.getVariable().getVariable() = v + ) +} + +from Assignment a, Variable v +where + useAndDef(a, v) and + exists(LoopStmt loop | a.getEnclosingStmt().getParent*() = loop | + not declaredInLoop(v, loop) + ) +select a, "The string " + v.getName() + " is built-up in a loop: use string buffer." diff --git a/java/ql/src/Performance/InefficientEmptyStringTest.java b/java/ql/src/Performance/InefficientEmptyStringTest.java new file mode 100644 index 00000000000..47425225811 --- /dev/null +++ b/java/ql/src/Performance/InefficientEmptyStringTest.java @@ -0,0 +1,17 @@ +// Inefficient version +class InefficientDBClient { + public void connect(String user, String pw) { + if (user.equals("") || "".equals(pw)) + throw new RuntimeException(); + ... + } +} + +// More efficient version +class EfficientDBClient { + public void connect(String user, String pw) { + if (user.length() == 0 || (pw != null && pw.length() == 0)) + throw new RuntimeException(); + ... + } +} diff --git a/java/ql/src/Performance/InefficientEmptyStringTest.qhelp b/java/ql/src/Performance/InefficientEmptyStringTest.qhelp new file mode 100644 index 00000000000..ccde60c2557 --- /dev/null +++ b/java/ql/src/Performance/InefficientEmptyStringTest.qhelp @@ -0,0 +1,55 @@ + + + + + +

    When checking whether a string s is empty, perhaps the most obvious +solution is to write something like s.equals("") (or "".equals(s)). +However, this actually carries a fairly significant overhead, because String.equals +performs a number of type tests and conversions before starting to compare the content +of the strings.

    + +
    + + +

    The preferred way of checking whether a string s is empty is to check if its +length is equal to zero. Thus, the condition is s.length() == 0. The length +method is implemented as a simple field access, and so should be noticeably faster than +calling equals.

    + +

    Note that in Java 6 and later, the String class has an isEmpty +method that checks whether a string is empty. If the codebase does not need to support Java 5, it may +be better to use that method instead.

    + +
    + + +

    In the following example, class InefficientDBClient uses equals to test +whether the strings user and pw are empty. Note that the test +"".equals(pw) guards against NullPointerException, but the test +user.equals("") throws a NullPointerException if user is +null.

    + +

    In contrast, the class EfficientDBClient uses length instead of +equals. The class preserves the behavior of InefficientDBClient by +guarding pw.length() == 0 but not user.length() == 0 with an explicit test +for null. Whether or not this guard is desirable depends on the intended behavior of +the program.

    + + + +
    + + + +
  • + Java Platform, Standard Edition 6, API Specification: + String.length(), + String.equals(). +
  • + + +
    +
    diff --git a/java/ql/src/Performance/InefficientEmptyStringTest.ql b/java/ql/src/Performance/InefficientEmptyStringTest.ql new file mode 100644 index 00000000000..57787900218 --- /dev/null +++ b/java/ql/src/Performance/InefficientEmptyStringTest.ql @@ -0,0 +1,21 @@ +/** + * @name Inefficient empty string test + * @description Checking a string for equality with an empty string is inefficient. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/inefficient-empty-string-test + * @tags efficiency + * maintainability + */ +import java + +from MethodAccess mc +where + mc.getQualifier().getType() instanceof TypeString and + mc.getMethod().hasName("equals") and + ( + mc.getArgument(0).(StringLiteral).getRepresentedString() = "" or + mc.getQualifier().(StringLiteral).getRepresentedString() = "" + ) +select mc, "Inefficient comparison to empty string, check for zero length instead." diff --git a/java/ql/src/Performance/InefficientKeySetIterator.java b/java/ql/src/Performance/InefficientKeySetIterator.java new file mode 100644 index 00000000000..ab69d0f5137 --- /dev/null +++ b/java/ql/src/Performance/InefficientKeySetIterator.java @@ -0,0 +1,25 @@ +// AVOID: Iterate the map using the key set. +class AddressBook { + private Map people = ...; + public String findId(String first, String last) { + for (String id : people.keySet()) { + Person p = people.get(id); + if (first.equals(p.firstName()) && last.equals(p.lastName())) + return id; + } + return null; + } +} + +// GOOD: Iterate the map using the entry set. +class AddressBook { + private Map people = ...; + public String findId(String first, String last) { + for (Entry entry: people.entrySet()) { + Person p = entry.getValue(); + if (first.equals(p.firstName()) && last.equals(p.lastName())) + return entry.getKey(); + } + return null; + } +} \ No newline at end of file diff --git a/java/ql/src/Performance/InefficientKeySetIterator.qhelp b/java/ql/src/Performance/InefficientKeySetIterator.qhelp new file mode 100644 index 00000000000..798b78fb788 --- /dev/null +++ b/java/ql/src/Performance/InefficientKeySetIterator.qhelp @@ -0,0 +1,47 @@ + + + + + +

    Java's Collections Framework provides several different ways of iterating +the contents of a map. You can retrieve the set of keys, the collection of values, or the +set of "entries" (which are, in effect, key/value pairs).

    + +

    The choice of iterator can affect performance. For example, it is considered bad practice to iterate +the key set of a map if the body of the loop performs a map lookup on each retrieved key +anyway.

    + +
    + + +

    Evaluate the requirements of the loop body. If it does not actually need the key +apart from looking it up in the map, iterate the map's values (obtained by a call to +values) instead. If the loop genuinely needs both key and value for +each mapping in the map, iterate the entry set (obtained by a call to +entrySet) and retrieve the key and value from each entry. This saves a +more expensive map lookup each time.

    + +
    + + +

    In the following example, the first version of the method findId iterates +the map people using the key set. This is inefficient because the body of the loop +needs to access the value for each key. In contrast, the second version iterates the map +using the entry set because the loop body needs both the key and the value for each mapping.

    + + + +
    + + + +
  • + Java Platform, Standard Edition 6, API Specification: + Map.entrySet(). +
  • + + +
    +
    diff --git a/java/ql/src/Performance/InefficientKeySetIterator.ql b/java/ql/src/Performance/InefficientKeySetIterator.ql new file mode 100644 index 00000000000..1e7e5ace7bb --- /dev/null +++ b/java/ql/src/Performance/InefficientKeySetIterator.ql @@ -0,0 +1,63 @@ +/** + * @name Inefficient use of key set iterator + * @description Iterating through the values of a map using the key set is inefficient. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/inefficient-key-set-iterator + * @tags efficiency + * maintainability + */ +import java + +/** A local variable that is initialized using a key-set iterator. */ +class KeySetIterator extends LocalVariableDecl { + KeySetIterator() { + exists(LocalVariableDeclExpr lvde, MethodAccess init | + lvde.getVariable() = this and + lvde.getInit() = init and + init.getMethod().hasName("iterator") and + init.getQualifier().(MethodAccess).getMethod().hasName("keySet") + ) + } + + LocalVariableDecl getBase() { + exists(LocalVariableDeclExpr lvde, MethodAccess init | + lvde.getVariable() = this and + lvde.getInit() = init and + init.getQualifier().(MethodAccess).getQualifier().(VarAccess).getVariable() = result + ) + } +} + +predicate isKeyNext(Expr e, KeySetIterator it) { + exists(MethodAccess ma | ma = e | + ma.getMethod().hasName("next") and + ma.getQualifier().(VarAccess).getVariable() = it + ) or + isKeyNext(e.(CastExpr).getExpr(), it) +} + +class Key extends LocalVariableDecl { + Key() { + exists(LocalVariableDeclExpr lvde, KeySetIterator it | + lvde.getVariable() = this and + isKeyNext(lvde.getInit(), it) + ) + } + + KeySetIterator getBase() { + exists(LocalVariableDeclExpr lvde | + lvde.getVariable() = this and + isKeyNext(lvde.getInit(), result) + ) + } +} + +from MethodAccess ma, Method get +where + ma.getMethod() = get and + get.hasName("get") and + ma.getAnArgument().(VarAccess).getVariable().(Key).getBase().getBase() + = ma.getQualifier().(VarAccess).getVariable() +select ma, "Inefficient use of key set iterator instead of entry set iterator." diff --git a/java/ql/src/Performance/InefficientOutputStream.qhelp b/java/ql/src/Performance/InefficientOutputStream.qhelp new file mode 100644 index 00000000000..6cee0000106 --- /dev/null +++ b/java/ql/src/Performance/InefficientOutputStream.qhelp @@ -0,0 +1,64 @@ + + + + +

    + The classes java.io.OutputStream + and java.io.FilterOutputStream only require + subclasses to implement the method write(byte b). + Typically, uses of OutputStreams will not write + single bytes, but an array via the write(byte[] b, int off, + int len) method. The default implementation of this method, + which you are not required to override, + calls write(byte b) for each byte in the array. If + this method involves I/O, such as accessing the network or disk, + this is likely to incur significant overhead. +

    +
    + + +

    + Always provide an implementation of the write(byte[] b, int + off, int len) method. +

    +
    + + + +

    + The following example shows a subclass + of OutputStream that simply wraps + a DigestOutputStream to confirm that the data it + writes to a file has the expected MD5 hash. Without an + implementation of write(byte[] b, int off, int len) + this will be very slow, because it makes a call + to DigestOutputStream.write(byte b) + and FileOutputStream.write(byte b) for each byte + written. +

    + + + +

    + The example can be updated to use a more efficient method. In this + case, calls to write(byte[] b, int off, int len) are + simply forwarded to DigestOutputStream.write(byte[] b, int + off, int len). +

    + + + +
    + + + +
  • + Java Platform, Standard Edition 8, API Documentation: + OutputStream.write(byte[] b, int off, int len), + FilterOutputStream.write(byte[] b, int off, int len). +
  • + +
    +
    diff --git a/java/ql/src/Performance/InefficientOutputStream.ql b/java/ql/src/Performance/InefficientOutputStream.ql new file mode 100644 index 00000000000..9b6b70e5e64 --- /dev/null +++ b/java/ql/src/Performance/InefficientOutputStream.ql @@ -0,0 +1,39 @@ +/** + * @name Inefficient output stream + * @description Using the default implementation of 'write(byte[],int,int)' + * provided by 'java.io.OutputStream' is very inefficient. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id java/inefficient-output-stream + * @tags efficiency + */ + +import java + +class InefficientWriteBytes extends Class { + InefficientWriteBytes() { + this.hasQualifiedName("java.io", "OutputStream") or + this.hasQualifiedName("java.io", "FilterOutputStream") + } +} + + +from Class c, Method m +where + not c.isAbstract() and + not c instanceof InefficientWriteBytes and + c.getASupertype() instanceof InefficientWriteBytes and + c.getAMethod() = m and + m.getName() = "write" and + m.getNumberOfParameters() = 1 and + m.getParameterType(0).(PrimitiveType).getName() = "int" and + exists(Method m2 | c.inherits(m2) | + m2.getName() = "write" and + m2.getNumberOfParameters() = 3 and + m2.getDeclaringType() instanceof InefficientWriteBytes + ) and + // If that method doesn't call write itself, then we don't have a problem. + // This is the case is some dummy implementations. + exists(MethodAccess ma | ma.getEnclosingCallable() = m | ma.getMethod().getName() = "write") +select c, "This class extends java.io.OutputStream and implements $@, but does not override write(byte[],int,int)", m, m.getName() diff --git a/java/ql/src/Performance/InefficientOutputStreamBad.java b/java/ql/src/Performance/InefficientOutputStreamBad.java new file mode 100644 index 00000000000..33dd1c75b88 --- /dev/null +++ b/java/ql/src/Performance/InefficientOutputStreamBad.java @@ -0,0 +1,27 @@ +public class DigestCheckingFileOutputStream extends OutputStream { + private DigestOutputStream digest; + private byte[] expectedMD5; + + public DigestCheckingFileOutputStream(File file, byte[] expectedMD5) + throws IOException, NoSuchAlgorithmException { + this.expectedMD5 = expectedMD5; + digest = new DigestOutputStream(new FileOutputStream(file), + MessageDigest.getInstance("MD5")); + } + + @Override + public void write(int b) throws IOException { + digest.write(b); + } + + @Override + public void close() throws IOException { + super.close(); + + digest.close(); + byte[] md5 = digest.getMessageDigest().digest(); + if (expectedMD5 != null && !Arrays.equals(expectedMD5, md5)) { + throw new InternalError(); + } + } +} diff --git a/java/ql/src/Performance/InefficientOutputStreamGood.java b/java/ql/src/Performance/InefficientOutputStreamGood.java new file mode 100644 index 00000000000..6f991c2c499 --- /dev/null +++ b/java/ql/src/Performance/InefficientOutputStreamGood.java @@ -0,0 +1,32 @@ +public class DigestCheckingFileOutputStream extends OutputStream { + private DigestOutputStream digest; + private byte[] expectedMD5; + + public DigestCheckingFileOutputStream(File file, byte[] expectedMD5) + throws IOException, NoSuchAlgorithmException { + this.expectedMD5 = expectedMD5; + digest = new DigestOutputStream(new FileOutputStream(file), + MessageDigest.getInstance("MD5")); + } + + @Override + public void write(int b) throws IOException { + digest.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + digest.write(b, off, len); + } + + @Override + public void close() throws IOException { + super.close(); + + digest.close(); + byte[] md5 = digest.getMessageDigest().digest(); + if (expectedMD5 != null && !Arrays.equals(expectedMD5, md5)) { + throw new InternalError(); + } + } +} diff --git a/java/ql/src/Performance/InefficientPrimConstructor.java b/java/ql/src/Performance/InefficientPrimConstructor.java new file mode 100644 index 00000000000..572eae9860b --- /dev/null +++ b/java/ql/src/Performance/InefficientPrimConstructor.java @@ -0,0 +1,18 @@ +public class Account { + private Integer balance; + public Account(Integer startingBalance) { + this.balance = startingBalance; + } +} + +public class BankManager { + public void openAccount(Customer c) { + ... + // AVOID: Inefficient primitive constructor + accounts.add(new Account(new Integer(0))); + // GOOD: Use 'valueOf' + accounts.add(new Account(Integer.valueOf(0))); + // GOOD: Rely on autoboxing + accounts.add(new Account(0)); + } +} \ No newline at end of file diff --git a/java/ql/src/Performance/InefficientPrimConstructor.qhelp b/java/ql/src/Performance/InefficientPrimConstructor.qhelp new file mode 100644 index 00000000000..eda20a82be2 --- /dev/null +++ b/java/ql/src/Performance/InefficientPrimConstructor.qhelp @@ -0,0 +1,62 @@ + + + + + +

    Primitive values (for example int, float, boolean) +all have corresponding reference types known as boxed types +(for example Integer, Float, Boolean). These boxed types +can be used when an actual object is required. While they all provide +constructors that take a primitive value of the appropriate type, it is usually considered +bad practice to call those constructors directly.

    + +

    Each boxed type provides a static valueOf method +that takes an argument of the appropriate primitive type and returns an object representing +it. The advantage of calling valueOf over calling a constructor is that it allows for +some caching of instances. By reusing these cached instances instead of +constructing new heap objects all the time, a significant amount of garbage collector effort can be saved.

    + +
    + + +

    In almost all circumstances, a call of, for example, Integer.valueOf(42) can be +used instead of new Integer(42).

    + +

    Note that sometimes you can rely on Java's +autoboxing feature, which implicitly calls valueOf. For details, see the +example.

    + +
    + + +

    The following example shows the three ways of creating a new integer. In the autoboxing example, +the zero is autoboxed to an Integer because the constructor Account takes +an argument of this type.

    + + + +
    + + + +
  • + J. Bloch, Effective Java (second edition), + Items 1 and 5. + Addison-Wesley, 2008. +
  • +
  • + Java Platform, Standard Edition 6, API Documentation: + Boolean.valueOf(), + Byte.valueOf(), + Short.valueOf(), + Integer.valueOf(), + Long.valueOf(), + Float.valueOf(), + Double.valueOf(). +
  • + + +
    +
    diff --git a/java/ql/src/Performance/InefficientPrimConstructor.ql b/java/ql/src/Performance/InefficientPrimConstructor.ql new file mode 100644 index 00000000000..773abe39e1a --- /dev/null +++ b/java/ql/src/Performance/InefficientPrimConstructor.ql @@ -0,0 +1,18 @@ +/** + * @name Inefficient primitive constructor + * @description Calling the constructor of a boxed type is inefficient. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/inefficient-boxed-constructor + * @tags efficiency + * maintainability + */ +import java + +from ClassInstanceExpr call, BoxedType type +where + type = call.getType() and + not call.getEnclosingCallable().getDeclaringType() = type +select call, "Inefficient constructor for " + type.getPrimitiveType().getName() + + " value, use " + type.getName() + ".valueOf(...) instead." diff --git a/java/ql/src/Performance/InefficientToArray.java b/java/ql/src/Performance/InefficientToArray.java new file mode 100644 index 00000000000..7ad20d7e1cd --- /dev/null +++ b/java/ql/src/Performance/InefficientToArray.java @@ -0,0 +1,28 @@ +class Company { + private List customers = ...; + + public Customer[] getCustomerArray() { + // AVOID: Inefficient call to 'toArray' with zero-length argument + return customers.toArray(new Customer[0]); + } +} + +class Company { + private List customers = ...; + + public Customer[] getCustomerArray() { + // GOOD: More efficient call to 'toArray' with argument that is big enough to store the list + return customers.toArray(new Customer[customers.size()]); + } +} + +class Company { + private static final Customer[] NO_CUSTOMERS = new Customer[0]; + + private List customers = ...; + + public Customer[] getCustomerArray() { + // GOOD: Reuse same zero-length array on every invocation + return customers.toArray(NO_CUSTOMERS); + } +} \ No newline at end of file diff --git a/java/ql/src/Performance/InefficientToArray.qhelp b/java/ql/src/Performance/InefficientToArray.qhelp new file mode 100644 index 00000000000..79bea193a5f --- /dev/null +++ b/java/ql/src/Performance/InefficientToArray.qhelp @@ -0,0 +1,61 @@ + + + + + +

    The java.util.Collection interface provides a toArray +method that can be used to convert a collection of objects into an array of a particular +type. This method takes an array as an argument, which is used for two purposes. +Firstly, it determines the type of the returned array. Secondly, if it is big enough to +hold all values in the collection, it is filled with those values and returned; +otherwise, a new array of sufficient size and the appropriate type is allocated and +filled.

    + +

    It is common to pass a fresh zero-length array to toArray, +simply because it is easy to construct one. Unfortunately, this allocation is wasteful, +because the array clearly is not big enough to hold the elements of the collection. This can cause +considerable garbage collector churn, impacting performance.

    + +
    + + +

    There are at least two ways to address this issue.

    + +

    The first is to always call toArray with a new array allocated with a +sufficient size to hold the contents of the collection. Usually, this involves calling +the collection's size method and allocating an array with that many +elements. While it may seem odd that adding a call to size improves performance, if you +do not pass a large enough array, the toArray method makes this call automatically. +Calling size explicitly and then calling toArray with a large enough array +avoids the possible creation of two arrays (one too small and consequently unused).

    + +

    The second approach is to add a static field holding a constant zero-length array to the +enclosing class, and pass that field to toArray. In this case, toArray +will end up allocating a new array in (almost) every case, but because the same zero-length array +is reused every time, there is almost no overhead. (Note that if toArray +is invoked on an empty collection, it will return the passed-in array. If your code expects +a new array from every invocation of toArray, you should use the first method.)

    + +
    + + +

    In the following example, the first version of class Company uses an inefficient +call to toArray by passing a zero-length array. The second and third version +illustrate the two ways of addressing this issue outlined above.

    + + + +
    + + + +
  • + Java Platform, Standard Edition 6, API Specification: + toArray. +
  • + + +
    +
    diff --git a/java/ql/src/Performance/InefficientToArray.ql b/java/ql/src/Performance/InefficientToArray.ql new file mode 100644 index 00000000000..2ae453f674b --- /dev/null +++ b/java/ql/src/Performance/InefficientToArray.ql @@ -0,0 +1,53 @@ +/** + * @name Inefficient toArray on zero-length argument + * @description Calling 'Collection.toArray' with a zero-length array argument is inefficient. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/inefficient-to-array + * @deprecated + */ +import java + +/* This method uses the toArray() method of a collection derived class, and passes in a + * zero-length prototype array argument. + * + * It is more efficient to use myCollection.toArray(new Foo[myCollection.size()]). + * If the array passed in is big enough to store all of the elements of the collection, + * then it is populated and returned directly. This avoids the need to create a + * second array (by reflection) to return as the result. + * + * The new array has to be created as the argument value. Values in both branches of + * a conditional has to be an empty array. + */ + +predicate emptyArrayLiteral(Expr e) { + // ArrayCreationExpr with zero-length init expr + exists(ArrayCreationExpr arr | arr = e | + exists(arr.getInit()) and + not exists(arr.getInit().getAnInit()) + ) + or + // ArrayCreationExpr where 0th dimension is zero literal + e.(ArrayCreationExpr).getDimension(0).(IntegerLiteral).getIntValue() = 0 + or + exists(ConditionalExpr cond | cond = e | + emptyArrayLiteral(cond.getTrueExpr()) and + emptyArrayLiteral(cond.getFalseExpr()) + ) +} + +class EmptyArrayLiteral extends Expr { + EmptyArrayLiteral() { + emptyArrayLiteral(this) + } +} + +from EmptyArrayLiteral l, MethodAccess ma, Method m, GenericInterface coll +where + coll.hasQualifiedName("java.util", "Collection") and + ma.getMethod() = m and + m.hasName("toArray") and + m.getDeclaringType().getASupertype*().getSourceDeclaration() = coll and + ma.getArgument(0) = l +select ma, "Collection.toArray(...) called with zero-length array argument." diff --git a/java/ql/src/Performance/InnerClassCouldBeStatic.qhelp b/java/ql/src/Performance/InnerClassCouldBeStatic.qhelp new file mode 100644 index 00000000000..b029582c193 --- /dev/null +++ b/java/ql/src/Performance/InnerClassCouldBeStatic.qhelp @@ -0,0 +1,64 @@ + + + + + +

    Nested classes allow logical grouping of related concerns, increasing encapsulation and +readability. However, there is a potential disadvantage when using them that you should be aware of.

    + +

    Any non-static nested class implicitly +holds onto its "enclosing instance". This means that:

    + +
      +
    • The nested class has an implicitly defined field. The field holds a reference to the instance of +the enclosing class that constructed the nested class.
    • +
    • The nested class's constructors have an implicit parameter. The parameter is used for passing in +the instance of the enclosing class. A reference to the instance is then stored in the field +mentioned above.
    • +
    + +

    Often, this is useful and necessary, because non-static nested class instances have +access to instance state on their enclosing classes. However, if this instance state is not needed +by the nested class, this makes nested class instances larger than necessary, and hidden references +to the enclosing classes are often the source of subtle memory leaks.

    + +
    + + +

    When a nested class does not need the enclosing instance, it is better to +declare the nested class static, avoiding the implicit field. As a result, +instances of the nested class become smaller, and hidden references to the enclosing class are +made explicit.

    + +

    If a reference to the enclosing class instance is required during construction of the nested class +instance (but not subsequently), the constructor of the nested class should be refactored so that +it is explicitly given a reference to the enclosing instance. +

    + +

    If the nested class refers to a type variable of an enclosing class instance, consider +parameterizing the nested class by that type variable. +

    + +
    + + + +
  • + J. Bloch, Effective Java (second edition), + Item 22. + Addison-Wesley, 2008. +
  • +
  • + Java Language Specification: + 8.1.3. Inner Classes and Enclosing Instances. +
  • +
  • + The Java Tutorials: + Nested Classes. +
  • + + +
    +
    diff --git a/java/ql/src/Performance/InnerClassCouldBeStatic.ql b/java/ql/src/Performance/InnerClassCouldBeStatic.ql new file mode 100644 index 00000000000..29728cd4fdd --- /dev/null +++ b/java/ql/src/Performance/InnerClassCouldBeStatic.ql @@ -0,0 +1,145 @@ +/** + * @name Inner class could be static + * @description A non-static nested class keeps a reference to the enclosing object, + * which makes the nested class bigger and may cause a memory leak. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/non-static-nested-class + * @tags efficiency + * maintainability + */ + +import java + +/** + * Is the field `f` inherited by the class `c`? This is a slightly imprecise, + * since package-protected fields are not inherited by classes in different + * packages, but it's enough for the purposes of this check. + */ +predicate inherits(Class c, Field f) { + f = c.getAField() or + (not f.isPrivate() and c.getASupertype+().getAField() = f) +} + +/** + * An access to a method or field that uses an enclosing instance + * of the type containing it. + */ +class EnclosingInstanceAccess extends Expr { + EnclosingInstanceAccess() { + exists(enclosingInstanceAccess(this)) + } + RefType getAnAccessedType() { + result = enclosingInstanceAccess(this) + } +} + +RefType enclosingInstanceAccess(Expr expr) { + exists(RefType enclosing | enclosing = expr.getEnclosingCallable().getDeclaringType() | + // A direct qualified `this` access that doesn't refer to the containing + // class must refer to an enclosing instance instead. + (result = expr.(ThisAccess).getType() and result != enclosing) + or + // A qualified `super` access qualified with a type that isn't the enclosing type. + (result = expr.(SuperAccess).getQualifier().(TypeAccess).getType() and result != enclosing) + or + // An unqualified `new` expression constructing a + // non-static type that needs an enclosing instance. + exists(ClassInstanceExpr new, InnerClass t | + new = expr and t = new.getType().(RefType).getSourceDeclaration() + | + result = t and + not exists(new.getQualifier()) and + not t.getEnclosingType*() = enclosing + ) or + // An unqualified method or field access to a member that isn't inherited + // must refer to an enclosing instance. + exists(FieldAccess fa | fa = expr | + result = fa.getField().getDeclaringType() and + not exists(fa.getQualifier()) and + not fa.getVariable().(Field).isStatic() and + not inherits(enclosing, fa.getVariable()) + ) or + exists(MethodAccess ma | ma = expr | + result = ma.getMethod().getDeclaringType() and + not exists(ma.getQualifier()) and + not ma.getMethod().isStatic() and + not exists(Method m | m.getSourceDeclaration() = ma.getMethod() | enclosing.inherits(m)) + ) + ) +} + +/** + * A nested class `c` could be static precisely when + * + * - it only accesses members of enclosing instances in its constructor + * (this includes field initializers); + * - it is not anonymous; + * - it is not a local class; + * - if its supertype or enclosing type is also nested, that type could be made static; + * - any classes nested within `c` only access members of enclosing instances of `c` in their constructors, + * and only extend classes that could be made static. + * + * Note that classes that are already static clearly "could" be static. + */ +predicate potentiallyStatic(InnerClass c) { + not exists(EnclosingInstanceAccess a, Method m | + m = a.getEnclosingCallable() and + m.getDeclaringType() = c + ) and + not c instanceof AnonymousClass and + not c instanceof LocalClass and + forall(InnerClass other | // If nested and non-static, ... + // ... all supertypes (which are from source), ... + (other = c.getASourceSupertype() and other.fromSource()) or + // ... and the enclosing type, ... + other = c.getEnclosingType() + | + // ... must be (potentially) static. + potentiallyStatic(other) + ) and + // No nested classes of `c` access an enclosing instance of `c` except in their constructors, i.e. + // for all accesses to a non-static member of an enclosing instance ... + forall(EnclosingInstanceAccess a, Method m | + // ... that occur in a method of a nested class of `c` ... + m = a.getEnclosingCallable() and m.getDeclaringType().getEnclosingType+() = c + | + // ... the access must be to a member of a type enclosed in `c` or `c` itself. + a.getAnAccessedType().getEnclosingType*() = c + ) and + // Any supertype of a class nested in `c` must be potentially static. + forall(InnerClass nested | nested.getEnclosingType+() = c | + forall(InnerClass superOfNested | superOfNested = nested.getASourceSupertype+() | + potentiallyStatic(superOfNested) + ) + ) +} + +/** + * A problematic class, meaning a class that could be static but isn't. + */ +class ProblematicClass extends InnerClass { + ProblematicClass() { + potentiallyStatic(this) + } + + /** + * Check for accesses to the enclosing instance in a constructor or field + * initializer. + */ + predicate usesEnclosingInstanceInConstructor() { + exists(EnclosingInstanceAccess a | + a.getEnclosingCallable() = this.getAConstructor() + ) + } +} + +from ProblematicClass c, string msg +where + c.fromSource() and + if c.usesEnclosingInstanceInConstructor() then + msg = " could be made static, since the enclosing instance is used only in its constructor." + else + msg = " should be made static, since the enclosing instance is not used." +select c, c.getName() + msg diff --git a/java/ql/src/Performance/NewStringString.java b/java/ql/src/Performance/NewStringString.java new file mode 100644 index 00000000000..17c167840ba --- /dev/null +++ b/java/ql/src/Performance/NewStringString.java @@ -0,0 +1,10 @@ +public void sayHello(String world) { + // AVOID: Inefficient 'String' constructor + String message = new String("hello "); + + // AVOID: Inefficient 'String' constructor + message = new String(message + world); + + // AVOID: Inefficient 'String' constructor + System.out.println(new String(message)); +} \ No newline at end of file diff --git a/java/ql/src/Performance/NewStringString.qhelp b/java/ql/src/Performance/NewStringString.qhelp new file mode 100644 index 00000000000..4c5be9a8341 --- /dev/null +++ b/java/ql/src/Performance/NewStringString.qhelp @@ -0,0 +1,47 @@ + + + + + +

    The String class is immutable, which means that there is +no way to change the string that it represents. Consequently, there is rarely a +need to copy a String object or construct a new instance based on +an existing string, for example by writing something like String hello = +new String("hello"). Furthermore, this practice is not memory efficient.

    + +
    + +

    The copied string is functionally indistinguishable from the argument that +was passed into the String constructor, so you can simply omit the constructor call and +use the argument passed into it directly. Unless an explicit copy of the argument string is needed, +this is a safe transformation.

    + +
    + + +

    The following example shows three cases of copying a string using the String +constructor, which is inefficient. In each case, simply removing the constructor call +new String and leaving the argument results in better code and less +memory churn.

    + + + +
    + + + +
  • + J. Bloch, Effective Java (second edition), + Item 5. + Addison-Wesley, 2008. +
  • +
  • + Java Platform, Standard Edition 6, API Specification: + String(String). +
  • + + +
    +
    diff --git a/java/ql/src/Performance/NewStringString.ql b/java/ql/src/Performance/NewStringString.ql new file mode 100644 index 00000000000..365a6cf886a --- /dev/null +++ b/java/ql/src/Performance/NewStringString.ql @@ -0,0 +1,18 @@ +/** + * @name Inefficient String constructor + * @description Using the 'String(String)' constructor is less memory efficient than using the + * constructor argument directly. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/inefficient-string-constructor + * @tags efficiency + * maintainability + */ +import java + +from ClassInstanceExpr e +where + e.getConstructor().getDeclaringType() instanceof TypeString and + e.getArgument(0).getType() instanceof TypeString +select e, "Inefficient new String(String) constructor." diff --git a/java/ql/src/Security/CWE/CWE-022/PathsCommon.qll b/java/ql/src/Security/CWE/CWE-022/PathsCommon.qll new file mode 100644 index 00000000000..f88d8c1e99a --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-022/PathsCommon.qll @@ -0,0 +1,79 @@ +import java +import semmle.code.java.controlflow.Guards + +abstract class PathCreation extends Expr { + abstract Expr getInput(); +} + +class PathsGet extends PathCreation, MethodAccess { + PathsGet() { + exists(Method m | m = this.getMethod() | + m.getDeclaringType() instanceof TypePaths and + m.getName() = "get" + ) + } + + override Expr getInput() { result = this.getAnArgument() } +} + +class FileSystemGetPath extends PathCreation, MethodAccess { + FileSystemGetPath() { + exists(Method m | m = this.getMethod() | + m.getDeclaringType() instanceof TypeFileSystem and + m.getName() = "getPath" + ) + } + + override Expr getInput() { result = this.getAnArgument() } +} + +class FileCreation extends PathCreation, ClassInstanceExpr { + FileCreation() { + this.getConstructedType() instanceof TypeFile + } + + override Expr getInput() { + result = this.getAnArgument() and + // Relevant arguments include those that are not a `File`. + not result.getType() instanceof TypeFile + } +} + +class FileWriterCreation extends PathCreation, ClassInstanceExpr { + FileWriterCreation() { + this.getConstructedType().getQualifiedName() = "java.io.FileWriter" + } + + override Expr getInput() { + result = this.getAnArgument() and + // Relevant arguments are those of type `String`. + result.getType() instanceof TypeString + } +} + +predicate inWeakCheck(Expr e) { + // None of these are sufficient to guarantee that a string is safe. + exists(MethodAccess m, Method def | m.getQualifier() = e and m.getMethod() = def | + def.getName() = "startsWith" or + def.getName() = "endsWith" or + def.getName() = "isEmpty" or + def.getName() = "equals" + ) or + // Checking against `null` has no bearing on path traversal. + exists(EqualityTest b | b.getAnOperand() = e | + b.getAnOperand() instanceof NullLiteral + ) +} + +// Ignore cases where the variable has been checked somehow, +// but allow some particularly obviously bad cases. +predicate guarded(VarAccess e) { + exists(PathCreation p | e = p.getInput()) and + exists(ConditionBlock cb, Expr c | + cb.getCondition().getAChildExpr*() = c and + c = e.getVariable().getAnAccess() and + cb.controls(e.getBasicBlock(), true) and + // Disallow a few obviously bad checks. + not inWeakCheck(c) + ) +} diff --git a/java/ql/src/Security/CWE/CWE-022/TaintedPath.java b/java/ql/src/Security/CWE/CWE-022/TaintedPath.java new file mode 100644 index 00000000000..9246a19dc8d --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-022/TaintedPath.java @@ -0,0 +1,24 @@ +public void sendUserFile(Socket sock, String user) { + BufferedReader filenameReader = new BufferedReader( + new InputStreamReader(sock.getInputStream(), "UTF-8")); + String filename = filenameReader.readLine(); + // BAD: read from a file using a path controlled by the user + BufferedReader fileReader = new BufferedReader( + new FileReader("/home/" + user + "/" + filename)); + String fileLine = fileReader.readLine(); + while(fileLine != null) { + sock.getOutputStream().write(fileLine.getBytes()); + fileLine = fileReader.readLine(); + } +} + +public void sendUserFileFixed(Socket sock, String user) { + // ... + + // GOOD: remove all dots and directory delimiters from the filename before using + String filename = filenameReader.readLine().replaceAll("\.", "").replaceAll("/", ""); + BufferedReader fileReader = new BufferedReader( + new FileReader("/home/" + user + "/" + filename)); + + // ... +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-022/TaintedPath.qhelp b/java/ql/src/Security/CWE/CWE-022/TaintedPath.qhelp new file mode 100644 index 00000000000..29bab0e42d2 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-022/TaintedPath.qhelp @@ -0,0 +1,46 @@ + + + +

    Accessing paths controlled by users can allow an attacker to access unexpected resources. This +can result in sensitive information being revealed or deleted, or an attacker being able to influence +behavior by modifying unexpected files.

    + +

    Paths that are naively constructed from data controlled by a user may contain unexpected special characters, +such as "..". Such a path may potentially point to any directory on the file system.

    + +
    + + +

    Validate user input before using it to construct a file path. Ideally, follow these rules:

    + +
      +
    • Do not allow more than a single "." character.
    • +
    • Do not allow directory separators such as "/" or "\" (depending on the file system).
    • +
    • Do not rely on simply replacing problematic sequences such as "../". For example, after applying this filter to +".../...//" the resulting string would still be "../".
    • +
    • Ideally use a whitelist of known good patterns.
    • +
    + +
    + + +

    In this example, a file name is read from a java.net.Socket and then used to access a file in the +user's home directory and send it back over the socket. However, a malicious user could enter a file name which contains special +characters. For example, the string "../../etc/passwd" will result in the code reading the file located at +"/home/[user]/../../etc/passwd", which is the system's password file. This file would then be sent back to the user, +giving them access to all the system's passwords.

    + + + +
    + + +
  • +OWASP: +Path Traversal. +
  • + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-022/TaintedPath.ql b/java/ql/src/Security/CWE/CWE-022/TaintedPath.ql new file mode 100644 index 00000000000..0b13c8c00ec --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-022/TaintedPath.ql @@ -0,0 +1,33 @@ +/** + * @name Uncontrolled data used in path expression + * @description Accessing paths influenced by users can allow an attacker to access unexpected resources. + * @kind problem + * @problem.severity error + * @precision high + * @id java/path-injection + * @tags security + * external/cwe/cwe-022 + * external/cwe/cwe-023 + * external/cwe/cwe-036 + * external/cwe/cwe-073 + */ +import java +import semmle.code.java.dataflow.FlowSources +import PathsCommon + +class TaintedPathConfig extends TaintTracking::Configuration { + TaintedPathConfig() { this = "TaintedPathConfig" } + override predicate isSource(DataFlow::Node source) { source instanceof RemoteUserInput } + override predicate isSink(DataFlow::Node sink) { + exists(Expr e | e = sink.asExpr() | e = any(PathCreation p).getInput() and not guarded(e)) + } + override predicate isSanitizer(DataFlow::Node node) { + exists(Type t | t = node.getType() | t instanceof BoxedType or t instanceof PrimitiveType) + } +} + +from RemoteUserInput u, PathCreation p, Expr e, TaintedPathConfig conf +where + e = p.getInput() and + conf.hasFlow(u, DataFlow::exprNode(e)) +select p, "$@ flows to here and is used in a path.", u, "User-provided value" diff --git a/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.qhelp b/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.qhelp new file mode 100644 index 00000000000..25fb4f6153a --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.qhelp @@ -0,0 +1,5 @@ + + + diff --git a/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.ql b/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.ql new file mode 100644 index 00000000000..5d145fe1ed1 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-022/TaintedPathLocal.ql @@ -0,0 +1,29 @@ +/** + * @name Local-user-controlled data in path expression + * @description Accessing paths influenced by users can allow an attacker to access unexpected resources. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/path-injection-local + * @tags security + * external/cwe/cwe-022 + * external/cwe/cwe-023 + * external/cwe/cwe-036 + * external/cwe/cwe-073 + */ +import java +import semmle.code.java.dataflow.FlowSources +import PathsCommon + +class TaintedPathLocalConfig extends TaintTracking::Configuration { + TaintedPathLocalConfig() { this = "TaintedPathLocalConfig" } + override predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput } + override predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(PathCreation p).getInput() } +} + +from LocalUserInput u, PathCreation p, Expr e, TaintedPathLocalConfig conf +where + e = p.getInput() and + conf.hasFlow(u, DataFlow::exprNode(e)) and + not guarded(e) +select p, "$@ flows to here and is used in a path.", u, "User-provided value" diff --git a/java/ql/src/Security/CWE/CWE-078/ExecCommon.qll b/java/ql/src/Security/CWE/CWE-078/ExecCommon.qll new file mode 100644 index 00000000000..2baf1282fce --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-078/ExecCommon.qll @@ -0,0 +1,20 @@ +import semmle.code.java.dataflow.FlowSources +import semmle.code.java.security.ExternalProcess + +private class RemoteUserInputToArgumentToExecFlowConfig extends TaintTracking::Configuration { + RemoteUserInputToArgumentToExecFlowConfig() { this = "ExecCommon::RemoteUserInputToArgumentToExecFlowConfig" } + override predicate isSource(DataFlow::Node src) { src instanceof RemoteUserInput } + override predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof ArgumentToExec } + override predicate isSanitizer(DataFlow::Node node) { node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType } +} + +/** + * Implementation of `ExecTainted.ql`. It is extracted to a QLL + * so that it can be excluded from `ExecUnescaped.ql` to avoid + * reporting overlapping results. + */ +predicate execTainted(RemoteUserInput source, ArgumentToExec execArg) { + exists(RemoteUserInputToArgumentToExecFlowConfig conf | + conf.hasFlow(source, DataFlow::exprNode(execArg)) + ) +} diff --git a/java/ql/src/Security/CWE/CWE-078/ExecRelative.java b/java/ql/src/Security/CWE/CWE-078/ExecRelative.java new file mode 100644 index 00000000000..7133d3c240a --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-078/ExecRelative.java @@ -0,0 +1,12 @@ +class Test { + public static void main(String[] args) { + // BAD: relative path + Runtime.getRuntime().exec("make"); + + // GOOD: absolute path + Runtime.getRuntime().exec("/usr/bin/make"); + + // GOOD: build an absolute path from known values + Runtime.getRuntime().exec(Paths.MAKE_PREFIX + "/bin/make"); + } +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-078/ExecRelative.qhelp b/java/ql/src/Security/CWE/CWE-078/ExecRelative.qhelp new file mode 100644 index 00000000000..efb3129d42e --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-078/ExecRelative.qhelp @@ -0,0 +1,37 @@ + + + +

    When a command is executed with a relative path, the runtime uses +the PATH environment variable to find which executable to run. Therefore, any +user who can change the PATH environment variable can cause the +software to run a different, malicious executable.

    + +
    + + +

    In most cases, simply use a command that has an absolute path instead of a relative path.

    + +

    In some cases, the location of the executable might be different on +different installations. In such cases, consider specifying the +location of key executables with some form of configuration. When using +this approach, be careful that the configuration system is not itself +vulnerable to malicious modifications.

    + + +
    + + + + + + + + + + + + +
    diff --git a/java/ql/src/Security/CWE/CWE-078/ExecRelative.ql b/java/ql/src/Security/CWE/CWE-078/ExecRelative.ql new file mode 100644 index 00000000000..8c5305b8ee4 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-078/ExecRelative.ql @@ -0,0 +1,26 @@ +/** + * @name Executing a command with a relative path + * @description Executing a command with a relative path is vulnerable to + * malicious changes in the PATH environment variable. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/relative-path-command + * @tags security + * external/cwe/cwe-078 + * external/cwe/cwe-088 + */ + +import semmle.code.java.Expr +import semmle.code.java.security.RelativePaths +import semmle.code.java.security.ExternalProcess + + +from ArgumentToExec argument, string command +where + ( + relativePath(argument, command) or + arrayStartingWithRelative(argument, command) + ) and + not shellBuiltin(command) +select argument, "Command with a relative path '" + command + "' is executed." diff --git a/java/ql/src/Security/CWE/CWE-078/ExecTainted.java b/java/ql/src/Security/CWE/CWE-078/ExecTainted.java new file mode 100644 index 00000000000..460f753a9dd --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-078/ExecTainted.java @@ -0,0 +1,9 @@ +class Test { + public static void main(String[] args) { + String script = System.getenv("SCRIPTNAME"); + if (script != null) { + // BAD: The script to be executed is controlled by the user. + Runtime.getRuntime().exec(script); + } + } +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-078/ExecTainted.qhelp b/java/ql/src/Security/CWE/CWE-078/ExecTainted.qhelp new file mode 100644 index 00000000000..c813d5c7fd3 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-078/ExecTainted.qhelp @@ -0,0 +1,47 @@ + + + +

    Code that passes user input directly to Runtime.exec, or +some other library routine that executes a command, allows the +user to execute malicious code.

    + +
    + + +

    If possible, use hard-coded string literals to specify the command to run +or library to load. Instead of passing the user input directly to the +process or library function, examine the user input and then choose +among hard-coded string literals.

    + +

    If the applicable libraries or commands cannot be determined at +compile time, then add code to verify that the user input string is +safe before using it.

    + +
    + + +

    The following example shows code that takes a shell script that can be changed +maliciously by a user, and passes it straight to Runtime.exec +without examining it first.

    + + + +
    + + +
  • +OWASP: +Command Injection. +
  • +
  • The CERT Oracle Secure Coding Standard for Java: + IDS07-J. Sanitize untrusted data passed to the Runtime.exec() method.
  • + + + + + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-078/ExecTainted.ql b/java/ql/src/Security/CWE/CWE-078/ExecTainted.ql new file mode 100644 index 00000000000..f86c6ca917f --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-078/ExecTainted.ql @@ -0,0 +1,21 @@ +/** + * @name Uncontrolled command line + * @description Using externally controlled strings in a command line is vulnerable to malicious + * changes in the strings. + * @kind problem + * @problem.severity error + * @precision high + * @id java/command-line-injection + * @tags security + * external/cwe/cwe-078 + * external/cwe/cwe-088 + */ + +import semmle.code.java.Expr +import semmle.code.java.dataflow.FlowSources +import semmle.code.java.security.ExternalProcess +import ExecCommon + +from StringArgumentToExec execArg, RemoteUserInput origin +where execTainted(origin, execArg) +select execArg, "$@ flows to here and is used in a command.", origin, "User-provided value" diff --git a/java/ql/src/Security/CWE/CWE-078/ExecTaintedLocal.qhelp b/java/ql/src/Security/CWE/CWE-078/ExecTaintedLocal.qhelp new file mode 100644 index 00000000000..d2f3018b0cd --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-078/ExecTaintedLocal.qhelp @@ -0,0 +1,5 @@ + + + diff --git a/java/ql/src/Security/CWE/CWE-078/ExecTaintedLocal.ql b/java/ql/src/Security/CWE/CWE-078/ExecTaintedLocal.ql new file mode 100644 index 00000000000..d8b72c698b7 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-078/ExecTaintedLocal.ql @@ -0,0 +1,27 @@ +/** + * @name Local-user-controlled command line + * @description Using externally controlled strings in a command line is vulnerable to malicious + * changes in the strings. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/command-line-injection-local + * @tags security + * external/cwe/cwe-078 + * external/cwe/cwe-088 + */ + +import semmle.code.java.Expr +import semmle.code.java.dataflow.FlowSources +import semmle.code.java.security.ExternalProcess + +class LocalUserInputToArgumentToExecFlowConfig extends TaintTracking::Configuration { + LocalUserInputToArgumentToExecFlowConfig() { this = "LocalUserInputToArgumentToExecFlowConfig" } + override predicate isSource(DataFlow::Node src) { src instanceof LocalUserInput } + override predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof ArgumentToExec } + override predicate isSanitizer(DataFlow::Node node) { node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType } +} + +from StringArgumentToExec execArg, LocalUserInput origin, LocalUserInputToArgumentToExecFlowConfig conf +where conf.hasFlow(origin, DataFlow::exprNode(execArg)) +select execArg, "$@ flows to here and is used in a command.", origin, "User-provided value" diff --git a/java/ql/src/Security/CWE/CWE-078/ExecUnescaped.java b/java/ql/src/Security/CWE/CWE-078/ExecUnescaped.java new file mode 100644 index 00000000000..1529c1fd203 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-078/ExecUnescaped.java @@ -0,0 +1,19 @@ +class Test { + public static void main(String[] args) { + // BAD: user input might include special characters such as ampersands + { + String latlonCoords = args[1]; + Runtime rt = Runtime.getRuntime(); + Process exec = rt.exec("cmd.exe /C latlon2utm.exe -" + latlonCoords); + } + + // GOOD: use an array of arguments instead of executing a string + { + String latlonCoords = args[1]; + Runtime rt = Runtime.getRuntime(); + Process exec = rt.exec(new String[] { + "c:\\path\to\latlon2utm.exe", + latlonCoords }); + } + } +} diff --git a/java/ql/src/Security/CWE/CWE-078/ExecUnescaped.qhelp b/java/ql/src/Security/CWE/CWE-078/ExecUnescaped.qhelp new file mode 100644 index 00000000000..bbae963b7d1 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-078/ExecUnescaped.qhelp @@ -0,0 +1,48 @@ + + + +

    Code that builds a command line by concatenating strings that have +been entered by a user allows the user to execute malicious code.

    + +
    + + +

    Execute external commands using an array of strings rather than a +single string. By using an array, many possible vulnerabilities in the +formatting of the string are avoided.

    + + +
    + + +

    In the following example, latlonCoords contains a string +that has been entered by a user but not validated by the program. This +allows the user to, for example, append an ampersand (&) followed by the command for +a malicious program to the end of the string. The ampersand instructs +Windows to execute another program. In the block marked 'BAD', latlonCoords +is passed to exec as part of a concatenated string, which allows more than one +command to be executed. However, in the block marked 'GOOD', latlonCoords +is passed as part of an array, which means that exec treats it only as +an argument.

    + + + +
    + + +
  • +OWASP: +Command Injection. +
  • +
  • The CERT Oracle Secure Coding Standard for Java: + IDS07-J. Sanitize untrusted data passed to the Runtime.exec() method.
  • + + + + + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-078/ExecUnescaped.ql b/java/ql/src/Security/CWE/CWE-078/ExecUnescaped.ql new file mode 100644 index 00000000000..d507ccdcc10 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-078/ExecUnescaped.ql @@ -0,0 +1,48 @@ +/** + * @name Building a command line with string concatenation + * @description Using concatenated strings in a command line is vulnerable to malicious + * insertion of special characters in the strings. + * @kind problem + * @problem.severity error + * @precision high + * @id java/concatenated-command-line + * @tags security + * external/cwe/cwe-078 + * external/cwe/cwe-088 + */ + +import semmle.code.java.Expr +import semmle.code.java.security.ExternalProcess +import ExecCommon + +/** + * Strings that are known to be sane by some simple local analysis. Such strings + * do not need to be escaped, because the programmer can predict what the string + * has in it. + */ +predicate saneString(Expr expr) { + expr instanceof StringLiteral or + expr instanceof NullLiteral or + exists(Variable var | var.getAnAccess() = expr and exists(var.getAnAssignedValue()) | + forall(Expr other | var.getAnAssignedValue() = other | saneString(other)) + ) +} + +predicate builtFromUncontrolledConcat(Expr expr) { + exists(AddExpr concatExpr | concatExpr = expr | + builtFromUncontrolledConcat(concatExpr.getAnOperand()) + ) or + exists(AddExpr concatExpr | concatExpr = expr | + exists(Expr arg | arg = concatExpr.getAnOperand() | not saneString(arg)) + ) or + exists(Expr other | builtFromUncontrolledConcat(other) | + exists(Variable var | var.getAnAssignedValue() = other and var.getAnAccess() = expr) + ) +} + + +from StringArgumentToExec argument +where + builtFromUncontrolledConcat(argument) and + not execTainted(_, argument) +select argument, "Command line is built with string concatenation." diff --git a/java/ql/src/Security/CWE/CWE-079/XSS.java b/java/ql/src/Security/CWE/CWE-079/XSS.java new file mode 100644 index 00000000000..273da84901c --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-079/XSS.java @@ -0,0 +1,8 @@ +public class XSS extends HttpServlet { + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + // BAD: a request parameter is written directly to an error response page + response.sendError(HttpServletResponse.SC_NOT_FOUND, + "The page \"" + request.getParameter("page") + "\" was not found."); + } +} diff --git a/java/ql/src/Security/CWE/CWE-079/XSS.qhelp b/java/ql/src/Security/CWE/CWE-079/XSS.qhelp new file mode 100644 index 00000000000..85bdfb130fd --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-079/XSS.qhelp @@ -0,0 +1,41 @@ + + + + + +

    Directly writing user input (for example, an HTTP request parameter) to a web page, +without properly sanitizing the input first, allows for a cross-site scripting vulnerability.

    + +
    + + +

    To guard against cross-site scripting, consider using contextual output encoding/escaping before +writing user input to the page, or one of the other solutions that are mentioned in the +reference.

    + +
    + + +

    The following example shows the page parameter being written directly to the server error page, +leaving the website vulnerable to cross-site scripting.

    + + + +
    + + + +
  • +OWASP: +XSS +(Cross Site Scripting) Prevention Cheat Sheet. +
  • +
  • +Wikipedia: Cross-site scripting. +
  • + + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-079/XSS.ql b/java/ql/src/Security/CWE/CWE-079/XSS.ql new file mode 100644 index 00000000000..73a30147b0d --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-079/XSS.ql @@ -0,0 +1,26 @@ +/** + * @name Cross-site scripting + * @description Writing user input directly to a web page + * allows for a cross-site scripting vulnerability. + * @kind problem + * @problem.severity error + * @precision high + * @id java/xss + * @tags security + * external/cwe/cwe-079 + */ +import java +import semmle.code.java.dataflow.FlowSources +import semmle.code.java.security.XSS + +class XSSConfig extends TaintTracking::Configuration2 { + XSSConfig() { this = "XSSConfig" } + override predicate isSource(DataFlow::Node source) { source instanceof RemoteUserInput } + override predicate isSink(DataFlow::Node sink) { sink instanceof XssSink } + override predicate isSanitizer(DataFlow::Node node) { node.getType() instanceof NumericType or node.getType() instanceof BooleanType } +} + +from XssSink sink, RemoteUserInput source, XSSConfig conf +where conf.hasFlow(source, sink) +select sink, "Cross-site scripting vulnerability due to $@.", + source, "user-provided value" diff --git a/java/ql/src/Security/CWE/CWE-079/XSSLocal.qhelp b/java/ql/src/Security/CWE/CWE-079/XSSLocal.qhelp new file mode 100644 index 00000000000..b35c7d781ff --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-079/XSSLocal.qhelp @@ -0,0 +1,5 @@ + + + diff --git a/java/ql/src/Security/CWE/CWE-079/XSSLocal.ql b/java/ql/src/Security/CWE/CWE-079/XSSLocal.ql new file mode 100644 index 00000000000..f8607c710a0 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-079/XSSLocal.ql @@ -0,0 +1,25 @@ +/** + * @name Cross-site scripting from local source + * @description Writing user input directly to a web page + * allows for a cross-site scripting vulnerability. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/xss-local + * @tags security + * external/cwe/cwe-079 + */ +import java +import semmle.code.java.dataflow.FlowSources +import semmle.code.java.security.XSS + +class XSSLocalConfig extends TaintTracking::Configuration2 { + XSSLocalConfig() { this = "XSSLocalConfig" } + override predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput } + override predicate isSink(DataFlow::Node sink) { sink instanceof XssSink } +} + +from XssSink sink, LocalUserInput source, XSSLocalConfig conf +where conf.hasFlow(source, sink) +select sink, "Cross-site scripting vulnerability due to $@.", + source, "user-provided value" diff --git a/java/ql/src/Security/CWE/CWE-089/HowToAddress.qhelp b/java/ql/src/Security/CWE/CWE-089/HowToAddress.qhelp new file mode 100644 index 00000000000..e8b0de186ed --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-089/HowToAddress.qhelp @@ -0,0 +1,32 @@ + + + +

    Usually, it is better to use a SQL prepared statement than to build a +complete SQL query with string concatenation. A prepared statement can +include a wildcard, written as a question mark (?), for each part of +the SQL query that is expected to be filled in by a different value +each time it is run. When the query is later executed, a value must be +supplied for each wildcard in the query.

    + +

    In the Java Persistence Query Language, it is better to use +queries with parameters than to build a complete query with string concatenation. +A Java Persistence query can include a parameter placeholder for each part of the query +that is expected to be filled in by a different value when run. +A parameter placeholder may be indicated by a colon (:) followed by a parameter name, +or by a question mark (?) followed by an integer position. +When the query is later executed, a value must be supplied for each parameter in the query, +using the setParameter method. +Specifying the query using the @NamedQuery annotation introduces an additional +level of safety: the query must be a constant string literal, preventing construction +by string concatenation, and the only way to fill in +values for parts of the query is by setting positional parameters.

    + +

    It is good practice to use prepared statements (in SQL) or query parameters +(in the Java Persistence Query Language) for supplying parameter values to a query, +whether or not any of the parameters are directly traceable to user input. +Doing so avoids any need to worry about quoting and escaping.

    + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-089/SqlInjectionLib.qll b/java/ql/src/Security/CWE/CWE-089/SqlInjectionLib.qll new file mode 100644 index 00000000000..f41140b2834 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-089/SqlInjectionLib.qll @@ -0,0 +1,49 @@ +/** Definitions used by the queries for database query injection. */ + +import semmle.code.java.Expr +import semmle.code.java.dataflow.FlowSources +import semmle.code.java.frameworks.android.SQLite +import semmle.code.java.frameworks.javaee.Persistence + +/** A sink for database query language injection vulnerabilities. */ +abstract class QueryInjectionSink extends DataFlow::ExprNode {} + +/** A sink for SQL injection vulnerabilities. */ +class SqlInjectionSink extends QueryInjectionSink { + SqlInjectionSink() { + this.getExpr() instanceof SqlExpr or + exists(SQLiteRunner s, MethodAccess m | m.getMethod() = s | + m.getArgument(s.sqlIndex()) = this.getExpr() + ) + } +} + +/** A sink for Java Persistence Query Language injection vulnerabilities. */ +class PersistenceQueryInjectionSink extends QueryInjectionSink { + PersistenceQueryInjectionSink() { + // the query (first) argument to a `createQuery` or `createNativeQuery` method on `EntityManager` + exists(MethodAccess call, TypeEntityManager em | call.getArgument(0) = this.getExpr() | + call.getMethod() = em.getACreateQueryMethod() or + call.getMethod() = em.getACreateNativeQueryMethod() + // note: `createNamedQuery` is safe, as it takes only the query name, + // and named queries can only be constructed using constants as the query text + ) + } +} + +private class QueryInjectionFlowConfig extends TaintTracking::Configuration { + QueryInjectionFlowConfig() { this = "SqlInjectionLib::QueryInjectionFlowConfig" } + override predicate isSource(DataFlow::Node src) { src instanceof RemoteUserInput } + override predicate isSink(DataFlow::Node sink) { sink instanceof QueryInjectionSink } + override predicate isSanitizer(DataFlow::Node node) { node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType } +} + +/** + * Implementation of `SqlTainted.ql`. This is extracted to a QLL so that it + * can be excluded from `SqlUnescaped.ql` to avoid overlapping results. + */ +predicate queryTaintedBy(QueryInjectionSink query, RemoteUserInput source) { + exists(QueryInjectionFlowConfig conf | + conf.hasFlow(source, query) + ) +} diff --git a/java/ql/src/Security/CWE/CWE-089/SqlTainted.java b/java/ql/src/Security/CWE/CWE-089/SqlTainted.java new file mode 100644 index 00000000000..19fc364fc6d --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-089/SqlTainted.java @@ -0,0 +1,17 @@ +{ + // BAD: the category might have SQL special characters in it + String category = System.getenv("ITEM_CATEGORY"); + Statement statement = connection.createStatement(); + String query1 = "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='" + + category + "' ORDER BY PRICE"; + ResultSet results = statement.executeQuery(query1); +} + +{ + // GOOD: use a prepared query + String category = System.getenv("ITEM_CATEGORY"); + String query2 = "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY=? ORDER BY PRICE"; + PreparedStatement statement = connection.prepareStatement(query2); + statement.setString(1, category); + ResultSet results = statement.executeQuery(); +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-089/SqlTainted.qhelp b/java/ql/src/Security/CWE/CWE-089/SqlTainted.qhelp new file mode 100644 index 00000000000..cab60173a62 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-089/SqlTainted.qhelp @@ -0,0 +1,84 @@ + + + +

    If a database query is built using string concatenation, and the +components of the concatenation include user input, a user +is likely to be able to run malicious database queries. +This applies to various database query languages, including +SQL and the Java Persistence Query Language.

    +
    + + + + + + +

    In the following example, the code runs a simple SQL query in two different ways.

    + +

    The first way involves building a query, query1, by concatenating an +environment variable with some string literals. The environment +variable can include special characters, so this code allows +for SQL injection attacks.

    + +

    The second way, which shows good practice, involves building a query, query2, with a +single string literal that includes a wildcard (?). The wildcard +is then given a value by calling setString. This +version is immune to injection attacks, because any special characters +in the environment variable are not given any special +treatment.

    + + +
    + + +

    The following code shows several different ways to run a Java Persistence query.

    + +

    The first example involves building a query, query1, by concatenating an +environment variable with some string literals. Just like the SQL example, the environment +variable can include special characters, so this code allows +for Java Persistence query injection attacks.

    + +

    The remaining examples demonstrate different methods for safely building a Java Persistence query with user-supplied values:

    +
      +
    1. query2 uses a single string literal that includes a placeholder for a parameter, indicated by a colon (:) and parameter name (category). +
    2. +
    3. query3 uses a single string literal that includes a placeholder for a parameter, indicated by a question mark (?) and position number (1). +
    4. +
    5. namedQuery1 is defined using the @NamedQuery annotation, +whose query attribute is a string literal that includes a placeholder for a parameter, +indicated by a colon (:) and parameter name (category). +
    6. +
    7. namedQuery2 is defined using the @NamedQuery annotation, +whose query attribute includes a placeholder for a parameter, +indicated by a question mark (?) and position number (1). +
    8. +
    +

    The parameter is then given a value by calling setParameter. These +versions are immune to injection attacks, because any special characters +in the environment variable or user-supplied value are not given any special treatment.

    + + + +
    + + + +
  • +OWASP: +SQL +Injection Prevention Cheat Sheet. +
  • +
  • The CERT Oracle Secure Coding Standard for Java: + IDS00-J. Prevent SQL injection.
  • +
  • The Java Tutorials: Using Prepared Statements.
  • +
  • The Java EE Tutorial: The Java Persistence Query Language.
  • + + + + + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-089/SqlTainted.ql b/java/ql/src/Security/CWE/CWE-089/SqlTainted.ql new file mode 100644 index 00000000000..91b2489b1da --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-089/SqlTainted.ql @@ -0,0 +1,21 @@ +/** + * @name Query built from user-controlled sources + * @description Building a SQL or Java Persistence query from user-controlled sources is vulnerable to insertion of + * malicious code by the user. + * @kind problem + * @problem.severity error + * @precision high + * @id java/sql-injection + * @tags security + * external/cwe/cwe-089 + */ + +import semmle.code.java.Expr +import semmle.code.java.dataflow.FlowSources +import SqlInjectionLib + + +from QueryInjectionSink query, RemoteUserInput source +where queryTaintedBy(query, source) +select query, "Query might include code from $@.", + source, "this user input" diff --git a/java/ql/src/Security/CWE/CWE-089/SqlTaintedLocal.qhelp b/java/ql/src/Security/CWE/CWE-089/SqlTaintedLocal.qhelp new file mode 100644 index 00000000000..accf2aee854 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-089/SqlTaintedLocal.qhelp @@ -0,0 +1,5 @@ + + + diff --git a/java/ql/src/Security/CWE/CWE-089/SqlTaintedLocal.ql b/java/ql/src/Security/CWE/CWE-089/SqlTaintedLocal.ql new file mode 100644 index 00000000000..b0b9aaf1c7c --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-089/SqlTaintedLocal.ql @@ -0,0 +1,27 @@ +/** + * @name Query built from local-user-controlled sources + * @description Building a SQL or Java Persistence query from user-controlled sources is vulnerable to insertion of + * malicious code by the user. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/sql-injection-local + * @tags security + * external/cwe/cwe-089 + */ + +import semmle.code.java.Expr +import semmle.code.java.dataflow.FlowSources +import SqlInjectionLib + +class LocalUserInputToQueryInjectionFlowConfig extends TaintTracking::Configuration { + LocalUserInputToQueryInjectionFlowConfig() { this = "LocalUserInputToQueryInjectionFlowConfig" } + override predicate isSource(DataFlow::Node src) { src instanceof LocalUserInput } + override predicate isSink(DataFlow::Node sink) { sink instanceof QueryInjectionSink } + override predicate isSanitizer(DataFlow::Node node) { node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType } +} + +from QueryInjectionSink query, LocalUserInput source, LocalUserInputToQueryInjectionFlowConfig conf +where conf.hasFlow(source, query) +select query, "Query might include code from $@.", + source, "this user input" diff --git a/java/ql/src/Security/CWE/CWE-089/SqlTaintedPersistence.java b/java/ql/src/Security/CWE/CWE-089/SqlTaintedPersistence.java new file mode 100644 index 00000000000..cc8d579e499 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-089/SqlTaintedPersistence.java @@ -0,0 +1,48 @@ +{ + // BAD: the category might have Java Persistence Query Language special characters in it + String category = System.getenv("ITEM_CATEGORY"); + Statement statement = connection.createStatement(); + String query1 = "SELECT p FROM Product p WHERE p.category LIKE '" + + category + "' ORDER BY p.price"; + Query q = entityManager.createQuery(query1); +} + +{ + // GOOD: use a named parameter and set its value + String category = System.getenv("ITEM_CATEGORY"); + String query2 = "SELECT p FROM Product p WHERE p.category LIKE :category ORDER BY p.price" + Query q = entityManager.createQuery(query2); + q.setParameter("category", category); +} + +{ + // GOOD: use a positional parameter and set its value + String category = System.getenv("ITEM_CATEGORY"); + String query3 = "SELECT p FROM Product p WHERE p.category LIKE ?1 ORDER BY p.price" + Query q = entityManager.createQuery(query3); + q.setParameter(1, category); +} + +{ + // GOOD: use a named query with a named parameter and set its value + @NamedQuery( + name="lookupByCategory", + query="SELECT p FROM Product p WHERE p.category LIKE :category ORDER BY p.price") + private static class NQ {} + ... + String category = System.getenv("ITEM_CATEGORY"); + Query namedQuery1 = entityManager.createNamedQuery("lookupByCategory"); + namedQuery1.setParameter("category", category); +} + +{ + // GOOD: use a named query with a positional parameter and set its value + @NamedQuery( + name="lookupByCategory", + query="SELECT p FROM Product p WHERE p.category LIKE ?1 ORDER BY p.price") + private static class NQ {} + ... + String category = System.getenv("ITEM_CATEGORY"); + Query namedQuery2 = entityManager.createNamedQuery("lookupByCategory"); + namedQuery2.setParameter(1, category); +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-089/SqlUnescaped.java b/java/ql/src/Security/CWE/CWE-089/SqlUnescaped.java new file mode 100644 index 00000000000..79f51234457 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-089/SqlUnescaped.java @@ -0,0 +1,17 @@ +{ + // BAD: the category might have SQL special characters in it + String category = getCategory(); + Statement statement = connection.createStatement(); + String query1 = "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='" + + category + "' ORDER BY PRICE"; + ResultSet results = statement.executeQuery(query1); +} + +{ + // GOOD: use a prepared query + String category = getCategory(); + String query2 = "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY=? ORDER BY PRICE"; + PreparedStatement statement = connection.prepareStatement(query2); + statement.setString(1, category); + ResultSet results = statement.executeQuery(); +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-089/SqlUnescaped.qhelp b/java/ql/src/Security/CWE/CWE-089/SqlUnescaped.qhelp new file mode 100644 index 00000000000..7415235610c --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-089/SqlUnescaped.qhelp @@ -0,0 +1,55 @@ + + + +

    Even when the components of a SQL query are not fully controlled by +a user, it is a vulnerability to concatenate those components into a +SQL query without neutralizing special characters. Perhaps a separate +vulnerability will allow the user to gain control of the component. As +well, a user who cannot gain full control of an input might influence +it enough to cause the SQL query to fail to run.

    + +
    + + + + + + + +

    In the following example, the code runs a simple SQL query in two different ways.

    + +

    The first way involves building a query, query1, by concatenating the +result of getCategory with some string literals. The result of +getCategory can include special characters, or +it might be refactored later so that it may return something that contains special characters.

    + +

    The second way, which shows good practice, involves building a query, query2, with +a single string literal that includes a wildcard (?). The wildcard +is then given a value by calling setString. This +version is immune to injection attacks, because any special characters +in the result of getCategory are not given any special +treatment.

    + + + +
    + + +
  • +OWASP: +SQL +Injection Prevention Cheat Sheet. +
  • +
  • The CERT Oracle Secure Coding Standard for Java: + IDS00-J. Prevent SQL injection.
  • +
  • The Java Tutorials: Using Prepared Statements.
  • + + + + + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-089/SqlUnescaped.ql b/java/ql/src/Security/CWE/CWE-089/SqlUnescaped.ql new file mode 100644 index 00000000000..590ad365dff --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-089/SqlUnescaped.ql @@ -0,0 +1,44 @@ +/** + * @name Query built without neutralizing special characters + * @description Building a SQL or Java Persistence query without escaping or otherwise neutralizing any special + * characters is vulnerable to insertion of malicious code. + * @kind problem + * @problem.severity error + * @precision high + * @id java/concatenated-sql-query + * @tags security + * external/cwe/cwe-089 + */ + +import semmle.code.java.security.SqlUnescapedLib +import SqlInjectionLib + +class UncontrolledStringBuilderSource extends DataFlow::ExprNode { + UncontrolledStringBuilderSource() { + exists(StringBuilderVar sbv | + uncontrolledStringBuilderQuery(sbv, _) and + this.getExpr() = sbv.getToStringCall() + ) + } +} + +class UncontrolledStringBuilderSourceFlowConfig extends TaintTracking::Configuration { + UncontrolledStringBuilderSourceFlowConfig() { this = "SqlUnescaped::UncontrolledStringBuilderSourceFlowConfig" } + override predicate isSource(DataFlow::Node src) { src instanceof UncontrolledStringBuilderSource } + override predicate isSink(DataFlow::Node sink) { sink instanceof QueryInjectionSink } + override predicate isSanitizer(DataFlow::Node node) { node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType } +} + +from QueryInjectionSink query, Expr uncontrolled +where + ( + builtFromUncontrolledConcat(query.getExpr(), uncontrolled) or + exists(StringBuilderVar sbv, UncontrolledStringBuilderSourceFlowConfig conf | + uncontrolledStringBuilderQuery(sbv, uncontrolled) and + conf.hasFlow(DataFlow::exprNode(sbv.getToStringCall()), query) + ) + ) and + not queryTaintedBy(query, _) +select + query, "Query might not neutralize special characters in $@.", + uncontrolled, "this expression" diff --git a/java/ql/src/Security/CWE/CWE-113/ResponseSplitting.java b/java/ql/src/Security/CWE/CWE-113/ResponseSplitting.java new file mode 100644 index 00000000000..b272e824552 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-113/ResponseSplitting.java @@ -0,0 +1,17 @@ +public class ResponseSplitting extends HttpServlet { + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + // BAD: setting a cookie with an unvalidated parameter + Cookie cookie = new Cookie("name", request.getParameter("name")); + response.addCookie(cookie); + + // GOOD: remove special characters before putting them in the header + String name = removeSpecial(request.getParameter("name")); + Cookie cookie2 = new Cookie("name", name); + response.addCookie(cookie2); + } + + private static String removeSpecial(String str) { + return str.replaceAll("[^a-zA-Z ]", ""); + } +} diff --git a/java/ql/src/Security/CWE/CWE-113/ResponseSplitting.qhelp b/java/ql/src/Security/CWE/CWE-113/ResponseSplitting.qhelp new file mode 100644 index 00000000000..87c2d03709b --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-113/ResponseSplitting.qhelp @@ -0,0 +1,41 @@ + + + + +

    Directly writing user input (for example, an HTTP request parameter) to an HTTP header +can lead to an HTTP response-splitting vulnerability. +If the user input includes blank lines in it, and if the servlet container does not itself +escape the blank lines, then a remote user can cause the response to turn into two separate +responses, one of which is controlled by the remote user.

    +
    + + +

    Guard against HTTP header splitting in the same way as guarding against cross-site scripting. +Before passing any data into HTTP headers, either check the data for special characters, or +escape any special characters that are present.

    +
    + + +

    The following example shows the 'name' parameter being written to a cookie +in two different ways. The first way writes it directly to the cookie, and thus +is vulnerable to response-splitting attacks. The second way first removes all special +characters, thus avoiding the potential problem.

    + + +
    + + +
  • +InfosecWriters: HTTP response splitting. +
  • +
  • +OWASP: +HTTP Response Splitting. +
  • +
  • +Wikipedia: HTTP response splitting. +
  • +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-113/ResponseSplitting.ql b/java/ql/src/Security/CWE/CWE-113/ResponseSplitting.ql new file mode 100644 index 00000000000..9d4a51f1f3a --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-113/ResponseSplitting.ql @@ -0,0 +1,27 @@ +/** + * @name HTTP response splitting + * @description Writing user input directly to an HTTP header + * makes code vulnerable to attack by header splitting. + * @kind problem + * @problem.severity error + * @precision high + * @id java/http-response-splitting + * @tags security + * external/cwe/cwe-113 + */ +import java +import ResponseSplitting + +class ResponseSplittingConfig extends TaintTracking::Configuration { + ResponseSplittingConfig() { this = "ResponseSplittingConfig" } + override predicate isSource(DataFlow::Node source) { + source instanceof RemoteUserInput and + not source instanceof WhitelistedSource + } + override predicate isSink(DataFlow::Node sink) { sink instanceof HeaderSplittingSink } +} + +from HeaderSplittingSink sink, RemoteUserInput source, ResponseSplittingConfig conf +where conf.hasFlow(source, sink) +select sink, "Response-splitting vulnerability due to this $@.", + source, "user-provided value" diff --git a/java/ql/src/Security/CWE/CWE-113/ResponseSplitting.qll b/java/ql/src/Security/CWE/CWE-113/ResponseSplitting.qll new file mode 100644 index 00000000000..80f08554cdc --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-113/ResponseSplitting.qll @@ -0,0 +1,34 @@ +import java +import semmle.code.java.frameworks.Servlets +import semmle.code.java.dataflow.FlowSources + +/** + * Header-splitting sinks. Expressions that end up in an HTTP header. + */ +class HeaderSplittingSink extends DataFlow::ExprNode { + HeaderSplittingSink() { + exists(ResponseAddCookieMethod m, MethodAccess ma | + ma.getMethod() = m and + this.getExpr() = ma.getArgument(0) + ) or + exists(ResponseAddHeaderMethod m, MethodAccess ma | + ma.getMethod() = m and + this.getExpr() = ma.getAnArgument() + ) or + exists(ResponseSetHeaderMethod m, MethodAccess ma | + ma.getMethod() = m and + this.getExpr() = ma.getAnArgument() + ) or + exists(JaxRsResponseBuilder builder, Method m | + m = builder.getAMethod() and m.getName() = "header" + | + this.getExpr() = m.getAReference().getArgument(1) + ) + } +} + +class WhitelistedSource extends RemoteUserInput { + WhitelistedSource() { + this.asExpr().(MethodAccess).getMethod() instanceof HttpServletRequestGetHeaderMethod + } +} diff --git a/java/ql/src/Security/CWE/CWE-113/ResponseSplittingLocal.qhelp b/java/ql/src/Security/CWE/CWE-113/ResponseSplittingLocal.qhelp new file mode 100644 index 00000000000..17afa6275fc --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-113/ResponseSplittingLocal.qhelp @@ -0,0 +1,5 @@ + + + diff --git a/java/ql/src/Security/CWE/CWE-113/ResponseSplittingLocal.ql b/java/ql/src/Security/CWE/CWE-113/ResponseSplittingLocal.ql new file mode 100644 index 00000000000..55ee289afbe --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-113/ResponseSplittingLocal.ql @@ -0,0 +1,25 @@ +/** + * @name HTTP response splitting from local source + * @description Writing user input directly to an HTTP header + * makes code vulnerable to attack by header splitting. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/http-response-splitting-local + * @tags security + * external/cwe/cwe-113 + */ +import java +import semmle.code.java.dataflow.FlowSources +import ResponseSplitting + +class ResponseSplittingLocalConfig extends TaintTracking::Configuration { + ResponseSplittingLocalConfig() { this = "ResponseSplittingLocalConfig" } + override predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput } + override predicate isSink(DataFlow::Node sink) { sink instanceof HeaderSplittingSink } +} + +from HeaderSplittingSink sink, LocalUserInput source, ResponseSplittingLocalConfig conf +where conf.hasFlow(source, sink) +select sink, "Response-splitting vulnerability due to this $@.", + source, "user-provided value" diff --git a/java/ql/src/Security/CWE/CWE-129/ArraySizing.qll b/java/ql/src/Security/CWE/CWE-129/ArraySizing.qll new file mode 100644 index 00000000000..b8b6668e40a --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-129/ArraySizing.qll @@ -0,0 +1,195 @@ +import java +import semmle.code.java.dataflow.DataFlow +import semmle.code.java.dataflow.DefUse +private import BoundingChecks + +/** + * If the `Array` accessed by the `ArrayAccess` is a fixed size, return the array size. + */ +int fixedArraySize(ArrayAccess arrayAccess) { + result = arrayAccess.getArray().(VarAccess).getVariable().getAnAssignedValue() + .(ArrayCreationExpr).getFirstDimensionSize() +} + +/** + * Holds if an `ArrayIndexOutOfBoundsException` is ever caught. + */ +private predicate arrayIndexOutOfBoundExceptionCaught(ArrayAccess arrayAccess) { + exists(TryStmt ts, CatchClause cc | + ( + ts.getBlock().getAChild*() = arrayAccess.getEnclosingStmt() or + ts.getAResourceDecl().getAChild*() = arrayAccess.getEnclosingStmt() or + ts.getAResourceExpr().getAChildExpr*() = arrayAccess + ) and + cc = ts.getACatchClause() + | + cc.getVariable().getType().(RefType).hasQualifiedName("java.lang", "ArrayIndexOutOfBoundsException") + ) +} + +/** + * A pointless loop, of the type seen frequently in Juliet tests, of the form: + * + * ``` + * while(true) { + * ... + * break; + * } + * ``` + */ +class PointlessLoop extends WhileStmt { + PointlessLoop() { + getCondition().(BooleanLiteral).getBooleanValue() = true and + // The only `break` must be the last statement. + forall(BreakStmt break | + break.(JumpStmt).getTarget() = this + | + this.getStmt().(Block).getLastStmt() = break + ) and + // No `continue` statements. + not exists(ContinueStmt continue | + continue.(JumpStmt).getTarget() = this + ) + } +} + +/** + * An `ArrayAccess` for which we can determine whether the index is appropriately bound checked. + * + * We only consider first dimension array accesses, and we only consider indices in loops, if it's + * obvious that the loop only executes once. + */ +class CheckableArrayAccess extends ArrayAccess { + CheckableArrayAccess() { + /* + * We are not interested in array accesses that don't access the first dimension. + */ + not this.getArray() instanceof ArrayAccess and + /* + * Array accesses within loops can make it difficult to verify whether the index is checked + * prior to access. Ignore "pointless" loops of the sort found in Juliet test cases. + */ + not exists(LoopStmt loop | + loop.getBody().getAChild*() = getEnclosingStmt() and + not loop instanceof PointlessLoop + ) and + // The possible exception is not caught + not arrayIndexOutOfBoundExceptionCaught(this) + } + + /** + * Holds if we believe this indexing expression can throw an `ArrayIndexOutOfBoundsException`. + */ + predicate canThrowOutOfBounds(Expr index) { + index = getIndexExpr() and + not ( + // There is a condition dominating this expression ensuring that the index is >= 0. + lowerBound(index) >= 0 + and + // There is a condition dominating this expression that ensures the index is less than the length. + lessthanLength(this) + ) + } + + /** + * Holds if we believe this indexing expression can throw an `ArrayIndexOutOfBoundsException` due + * to the array being initialized with `sizeExpr`, which may be zero. + */ + predicate canThrowOutOfBoundsDueToEmptyArray(Expr sizeExpr, ArrayCreationExpr arrayCreation) { + /* + * Find an `ArrayCreationExpr` for the array used in this indexing operation. + */ + exists(VariableAssign assign | + assign.getSource() = arrayCreation and + defUsePair(assign, this.getArray()) + ) and + /* + * If the array access is protected by a conditional that verifies the index is less than the array + * length, then the array will never be accessed if the size is zero. + */ + not lessthanLength(this) and + /* + * Verify that the size expression is never checked to be greater than 0. + */ + sizeExpr = arrayCreation.getDimension(0) and + not lowerBound(sizeExpr) > 0 + } +} + +/** + * A source of "flow" which has an upper or lower bound. + */ +abstract class BoundedFlowSource extends DataFlow::Node { + + /** + * Return a lower bound for the input, if possible. + */ + abstract int lowerBound(); + + /** + * Return an upper bound for the input, if possible. + */ + abstract int upperBound(); + + /** + * Return a description for this flow source, suitable for putting in an alert message. + */ + abstract string getDescription(); +} + +/** + * Input that is constructed using a `Random` value. + */ +class RandomValueFlowSource extends BoundedFlowSource { + RandomValueFlowSource() { + exists(RefType random, MethodAccess nextAccess | + random.hasQualifiedName("java.util", "Random") + | + nextAccess.getCallee().getDeclaringType().getAnAncestor() = random and + nextAccess.getCallee().getName().matches("next%") and + nextAccess = this.asExpr() + ) + } + + int lowerBound() { + // If this call is to `nextInt()`, the lower bound is zero. + this.asExpr().(MethodAccess).getCallee().hasName("nextInt") and + this.asExpr().(MethodAccess).getNumArgument() = 1 and + result = 0 + } + + int upperBound() { + /* + * If this call specified an argument to `nextInt()`, and that argument is a compile time constant, + * it forms the upper bound. + */ + this.asExpr().(MethodAccess).getCallee().hasName("nextInt") and + this.asExpr().(MethodAccess).getNumArgument() = 1 and + result = this.asExpr().(MethodAccess).getArgument(0).(CompileTimeConstantExpr).getIntValue() + } + + string getDescription() { + result = "Random value" + } +} + +/** + * A compile time constant expression that evaluates to a numeric type. + */ +class NumericLiteralFlowSource extends BoundedFlowSource { + NumericLiteralFlowSource() { + exists(this.asExpr().(CompileTimeConstantExpr).getIntValue()) + } + + int lowerBound() { + result = this.asExpr().(CompileTimeConstantExpr).getIntValue() + } + + int upperBound() { + result = this.asExpr().(CompileTimeConstantExpr).getIntValue() + } + + string getDescription() { + result = "Literal value " + this.asExpr().(CompileTimeConstantExpr).getIntValue() + } +} diff --git a/java/ql/src/Security/CWE/CWE-129/BoundingChecks.qll b/java/ql/src/Security/CWE/CWE-129/BoundingChecks.qll new file mode 100644 index 00000000000..ea56fce9824 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-129/BoundingChecks.qll @@ -0,0 +1,62 @@ +/** + * Provides classes and predicates for determining upper and lower bounds on a value determined by bounding checks that + * have been made on dominant paths. + */ + +import java +private import semmle.code.java.controlflow.Guards + +/** + * Holds if the given `ComparisonExpr` is thought to be true when `VarAccess` is accessed. + */ +private predicate conditionHolds(ComparisonExpr ce, VarAccess va) { + exists(ConditionBlock cond | + cond.getCondition() = ce and + cond.controls(va.getBasicBlock(), true) + ) +} + +/** + * Determine an inclusive lower-bound - if possible - for the value accessed by the given `VarAccess`, + * based upon the conditionals that hold at the point the variable is accessed. + */ +int lowerBound(VarAccess va) { + exists(ComparisonExpr greaterThanValue | + // This condition should hold when the variable is later accessed. + conditionHolds(greaterThanValue, va) + | + greaterThanValue.getGreaterOperand() = va.getVariable().getAnAccess() and + if greaterThanValue.isStrict() then + // value > i, so value has a lower bound of i + 1 + result = greaterThanValue.getLesserOperand().(CompileTimeConstantExpr).getIntValue() + 1 + else + // value >= i, so value has a lower bound of i + result = greaterThanValue.getLesserOperand().(CompileTimeConstantExpr).getIntValue() + ) +} + +/** + * Holds if the index expression is a `VarAccess`, where the variable has been confirmed to be less + * than the length. + */ +predicate lessthanLength(ArrayAccess a) { + exists(ComparisonExpr lessThanLength, VarAccess va | + va = a.getIndexExpr() and + conditionHolds(lessThanLength, va) + | + lessThanLength.getGreaterOperand().(FieldAccess).getQualifier() = arrayReference(a) and + lessThanLength.getGreaterOperand().(FieldAccess).getField().hasName("length") and + lessThanLength.getLesserOperand() = va.getVariable().getAnAccess() and + lessThanLength.isStrict() + ) +} + +/** + * Return all other references to the array accessed in the `ArrayAccess`. + */ +private pragma[nomagic] Expr arrayReference(ArrayAccess arrayAccess) { + // Array is stored in a variable. + result = arrayAccess.getArray().(VarAccess).getVariable().getAnAccess() or + // Array is returned from a method. + result.(MethodAccess).getMethod() = arrayAccess.getArray().(MethodAccess).getMethod() +} diff --git a/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstruction.java b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstruction.java new file mode 100644 index 00000000000..ff9ff057332 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstruction.java @@ -0,0 +1,28 @@ +public class ImproperValidationOfArrayIndex extends HttpServlet { + + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + try { + // User provided value + int numberOfItems = Integer.parseInt(request.getParameter("numberOfItems").trim()); + + if (numberOfItems >= 0) { + /* + * BAD numberOfItems may be zero, which would cause the array indexing operation to + * throw an ArrayIndexOutOfBoundsException + */ + String items = new String[numberOfItems]; + items[0] = "Item 1"; + } + + if (numberOfItems > 0) { + /* + * GOOD numberOfItems must be greater than zero, so the indexing succeeds. + */ + String items = new String[numberOfItems]; + items[0] = "Item 1"; + } + + } catch (NumberFormatException e) { } + } +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstruction.qhelp b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstruction.qhelp new file mode 100644 index 00000000000..7cfa72612fd --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstruction.qhelp @@ -0,0 +1,38 @@ + + + +

    Using unvalidated input when specifying the size of a newly created array can result in the +creation of an array with size zero. If this array is subsequently accessed without further checks, +an ArrayIndexOutOfBoundsException may be thrown, because there is no guarantee that +the array is not empty.

    + +

    This problem occurs when user input is used as the size during array initialization, either directly + or following one or more calculations. If the user input is unvalidated, it may cause the size of + the array to be zero.

    +
    + +

    +The size used in the array initialization should be verified to be greater than zero before being +used. Alternatively, the array access may be protected by a conditional check that ensures it is +only accessed if the index is less than the array size.

    +
    + +

    The following program constructs an array with the size specified by some user input:

    + +

    The first array construction is protected by a condition that checks if the user input is zero +or more. However, if the user provides 0 as the numberOfItems parameter, +then an empty array is created, and any array access would fail with an + ArrayIndexOutOfBoundsException.

    +

    The second array construction is protected by a condition that checks if the user input is +greater than zero. The array will therefore never be empty, and the following array +access will not throw an ArrayIndexOutOfBoundsException.

    +
    + + + +
  • Java API: ArrayIndexOutOfBoundsException.
  • + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstruction.ql b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstruction.ql new file mode 100644 index 00000000000..90879bcfb1c --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstruction.ql @@ -0,0 +1,31 @@ +/** + * @name Improper validation of user-provided size used for array construction + * @description Using unvalidated external input as the argument to a construction of an array can lead to index out of bound exceptions. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/improper-validation-of-array-construction + * @tags security + * external/cwe/cwe-129 + */ + +import java +import ArraySizing +import semmle.code.java.dataflow.FlowSources + +class Conf extends TaintTracking::Configuration { + Conf() { this = "RemoteUserInputTocanThrowOutOfBoundsDueToEmptyArrayConfig" } + override predicate isSource(DataFlow::Node source) { source instanceof RemoteUserInput } + override predicate isSink(DataFlow::Node sink) { + any(CheckableArrayAccess caa).canThrowOutOfBoundsDueToEmptyArray(sink.asExpr(), _) + } +} + +from RemoteUserInput source, Expr sizeExpr, ArrayCreationExpr arrayCreation, CheckableArrayAccess arrayAccess +where + arrayAccess.canThrowOutOfBoundsDueToEmptyArray(sizeExpr, arrayCreation) and + any(Conf conf).hasFlow(source, DataFlow::exprNode(sizeExpr)) +select arrayAccess.getIndexExpr(), + "The $@ is accessed here, but the array is initialized using $@ which may be zero.", + arrayCreation, "array", + source, "User-provided value" diff --git a/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstructionCodeSpecified.java b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstructionCodeSpecified.java new file mode 100644 index 00000000000..14ab323b68f --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstructionCodeSpecified.java @@ -0,0 +1,23 @@ +public class PossibleArrayIndexOutOfBounds { + + public static void main(String[] args) { + int numberOfItems = new Random().nextInt(10); + + if (numberOfItems >= 0) { + /* + * BAD numberOfItems may be zero, which would cause the array indexing operation to + * throw an ArrayIndexOutOfBoundsException + */ + String items = new String[numberOfItems]; + items[0] = "Item 1"; + } + + if (numberOfItems > 0) { + /* + * GOOD numberOfItems must be greater than zero, so the indexing succeeds. + */ + String items = new String[numberOfItems]; + items[0] = "Item 1"; + } + } +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstructionCodeSpecified.qhelp b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstructionCodeSpecified.qhelp new file mode 100644 index 00000000000..dafc693351c --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstructionCodeSpecified.qhelp @@ -0,0 +1,36 @@ + + + +

    +Constructing an array using a size that may be zero can result in the creation of an empty array. +If an empty array is accessed without further checks, an ArrayIndexOutOfBoundsException +is thrown.

    +

    +This can happen when a fixed value of zero, or a random value that may be zero, is used as the size +directly.

    +
    + +

    +The size used in the array initialization should be verified to be greater than zero before being used. + Alternatively, the array access may be placed within a conditional that ensures it is only accessed if + the index is less than the array size.

    +
    + +

    The following program constructs an array with the size specified by some random value:

    + +

    The first array construction is protected by a condition that checks if the random value is zero +or more. However, if the random value is 0 then an empty array is created, and any +array access would fail with an ArrayIndexOutOfBoundsException.

    +

    The second array construction is protected by a condition that checks if the random value is +greater than zero. The array will therefore never be empty, and the following array +access will not throw an ArrayIndexOutOfBoundsException.

    +
    + + + +
  • Java API: ArrayIndexOutOfBoundsException.
  • + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstructionCodeSpecified.ql b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstructionCodeSpecified.ql new file mode 100644 index 00000000000..45f058e4af2 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstructionCodeSpecified.ql @@ -0,0 +1,35 @@ +/** + * @name Improper validation of code-specified size used for array construction + * @description Using a code-specified value that may be zero as the argument to + * a construction of an array can lead to index out of bound exceptions. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/improper-validation-of-array-construction-code-specified + * @tags security + * external/cwe/cwe-129 + */ + +import java +import ArraySizing + +class BoundedFlowSourceConf extends DataFlow::Configuration { + BoundedFlowSourceConf() { this = "BoundedFlowSource" } + override predicate isSource(DataFlow::Node source) { + source instanceof BoundedFlowSource and + // There is not a fixed lower bound which is greater than zero. + not source.(BoundedFlowSource).lowerBound() > 0 + } + override predicate isSink(DataFlow::Node sink) { + any(CheckableArrayAccess caa).canThrowOutOfBoundsDueToEmptyArray(sink.asExpr(), _) + } +} + +from BoundedFlowSource source, Expr sizeExpr, ArrayCreationExpr arrayCreation, CheckableArrayAccess arrayAccess +where + arrayAccess.canThrowOutOfBoundsDueToEmptyArray(sizeExpr, arrayCreation) and + any(BoundedFlowSourceConf conf).hasFlow(source, DataFlow::exprNode(sizeExpr)) +select arrayAccess.getIndexExpr(), + "The $@ is accessed here, but the array is initialized using $@ which may be zero.", + arrayCreation, "array", + source, source.getDescription().toLowerCase() diff --git a/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstructionLocal.qhelp b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstructionLocal.qhelp new file mode 100644 index 00000000000..3f467edeca3 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstructionLocal.qhelp @@ -0,0 +1,5 @@ + + + diff --git a/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstructionLocal.ql b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstructionLocal.ql new file mode 100644 index 00000000000..9477ab0738c --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayConstructionLocal.ql @@ -0,0 +1,32 @@ +/** + * @name Improper validation of local user-provided size used for array construction + * @description Using unvalidated local input as the argument to + * a construction of an array can lead to index out of bound exceptions. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/improper-validation-of-array-construction-local + * @tags security + * external/cwe/cwe-129 + */ + +import java +import ArraySizing +import semmle.code.java.dataflow.FlowSources + +class Conf extends TaintTracking::Configuration { + Conf() { this = "LocalUserInputTocanThrowOutOfBoundsDueToEmptyArrayConfig" } + override predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput } + override predicate isSink(DataFlow::Node sink) { + any(CheckableArrayAccess caa).canThrowOutOfBoundsDueToEmptyArray(sink.asExpr(), _) + } +} + +from LocalUserInput source, Expr sizeExpr, ArrayCreationExpr arrayCreation, CheckableArrayAccess arrayAccess +where + arrayAccess.canThrowOutOfBoundsDueToEmptyArray(sizeExpr, arrayCreation) and + any(Conf conf).hasFlow(source, DataFlow::exprNode(sizeExpr)) +select arrayAccess.getIndexExpr(), + "The $@ is accessed here, but the array is initialized using $@ which may be zero.", + arrayCreation, "array", + source, "User-provided value" diff --git a/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndex.java b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndex.java new file mode 100644 index 00000000000..30857592634 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndex.java @@ -0,0 +1,29 @@ +public class ImproperValidationOfArrayIndex extends HttpServlet { + + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + String[] productDescriptions = new String[] { "Chocolate bar", "Fizzy drink" }; + + // User provided value + String productID = request.getParameter("productID"); + try { + int productID = Integer.parseInt(userProperty.trim()); + + /* + * BAD Array is accessed without checking if the user provided value is out of + * bounds. + */ + String productDescription = productDescriptions[productID]; + + if (productID >= 0 && productID < productDescriptions.length) { + // GOOD We have checked that the array index is valid first + productDescription = productDescriptions[productID]; + } else { + productDescription = "No product for that ID"; + } + + response.getWriter().write(productDescription); + + } catch (NumberFormatException e) { } + } +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndex.qhelp b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndex.qhelp new file mode 100644 index 00000000000..fc41cb72e3a --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndex.qhelp @@ -0,0 +1,36 @@ + + + +

    Using unvalidated input as part of an index into the array can cause the array access + to throw an ArrayIndexOutOfBoundsException. This is because there is no guarantee + that the index provided is within the bounds of the array.

    + +

    This problem occurs when user input is used as an array index, either directly or following one +or more calculations. If the user input is unsanitized, it may be any value, which could result in +either a negative index, or an index which is larger than the size of the array, either of which +would result in an ArrayIndexOutOfBoundsException.

    +
    + +

    +The index used in the array access should be checked against the bounds of the array before being + used. The index should be smaller than the array size, and it should not be negative.

    +
    + +

    The following program accesses an element from a fixed size constant array:

    + +

    The first access of the productDescriptions array uses the user-provided value as + the index without performing any checks. If the user provides a negative value, or a value larger + than the size of the array, then an ArrayIndexOutOfBoundsException may be thrown.

    +

    The second access of the productDescriptions array is contained within a conditional + expression that verifies the user-provided value is a valid index into the array. This ensures that + the access operation never throws an ArrayIndexOutOfBoundsException.

    +
    + + + +
  • Java API: ArrayIndexOutOfBoundsException.
  • + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndex.ql b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndex.ql new file mode 100644 index 00000000000..da9c0a243ff --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndex.ql @@ -0,0 +1,31 @@ +/** + * @name Improper validation of user-provided array index + * @description Using external input as an index to an array, without proper validation, can lead to index out of bound exceptions. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/improper-validation-of-array-index + * @tags security + * external/cwe/cwe-129 + */ + +import java +import ArraySizing +import semmle.code.java.dataflow.FlowSources + +class Conf extends TaintTracking::Configuration { + Conf() { this = "RemoteUserInputTocanThrowOutOfBoundsDueToEmptyArrayConfig" } + override predicate isSource(DataFlow::Node source) { source instanceof RemoteUserInput } + override predicate isSink(DataFlow::Node sink) { + any(CheckableArrayAccess caa).canThrowOutOfBounds(sink.asExpr()) + } + override predicate isSanitizer(DataFlow::Node node) { node.getType() instanceof BooleanType } +} + +from RemoteUserInput source, Expr index, CheckableArrayAccess arrayAccess +where + arrayAccess.canThrowOutOfBounds(index) and + any(Conf conf).hasFlow(source, DataFlow::exprNode(index)) +select arrayAccess.getIndexExpr(), + "$@ flows to here and is used as an index causing an ArrayIndexOutOfBoundsException.", + source, "User-provided value" diff --git a/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndexCodeSpecified.java b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndexCodeSpecified.java new file mode 100644 index 00000000000..97c02d3dedf --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndexCodeSpecified.java @@ -0,0 +1,26 @@ +public class ImproperValidationOfArrayIndex extends HttpServlet { + + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + // Search for products in productDescriptions that match the search term + String searchTerm = request.getParameter("productSearchTerm"); + int foundProductID = -1; + for (int i = 0; i < productDescriptions.length; i++) { + if (productDescriptions[i].contains(searchTerm)) { + // Found matching product + foundProductID = i; + break; + } + } + + // BAD We may not have found a product in which case the index would be -1 + response.getWriter().write(productDescriptions[foundProductID]); + + if (foundProductID >= 0) { + // GOOD We have checked we found a product first + response.getWriter().write(productDescriptions[foundProductID]); + } else { + response.getWriter().write("No product found"); + } + } +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndexCodeSpecified.qhelp b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndexCodeSpecified.qhelp new file mode 100644 index 00000000000..1caeae11a69 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndexCodeSpecified.qhelp @@ -0,0 +1,48 @@ + + + +

    Using a hard-coded or randomly provided value as the index to an array access can cause that array + access to throw an ArrayIndexOutOfBoundsException if the value is outside the bounds of + that array.

    + +

    This problem occurs when a literal value, or a value generated using the Random, is + used as an index for an array access operation. If one or more of the range of values produced by + the random operation, or the fixed value of the literal, is outside the bounds of the array then + this can cause an ArrayIndexOutOfBoundsException.

    +
    + +

    +If the index is a literal value, then the literal value may need to be modified to specify an index that is + guaranteed to lie within the bounds of the array. Alternatively, the literal value may represent a default + value that was never intended to be used in the array access, in which case the logic should be modified to + ensure the default value is never used. +

    +

    +For indices that flow from randomly generated values, either the random operation should be modified to generate + a value that is guaranteed to be within the bounds of the array, or the array access should be protected by + suitable conditional checks that verify the index is smaller than the length and larger than or equal to zero. +

    +
    + +

    The following program searches through an array to find the index at which some search text matches:

    + +

    +If the search text is not found, foundProductID is set to the default value - specified as + -1. In the first access, foundProductID is used without checking whether the + index is -1. This code can therefore throw a ArrayIndexOutOfBoundsException if + the search text is not found. +

    +

    +In the second case, the array access is protected by a conditional that verifies the index is greater than + or equal to zero. This ensures that an ArrayIndexOutOfBoundsException cannot be thrown. +

    +
    + + + +
  • Java API: ArrayIndexOutOfBoundsException.
  • + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndexCodeSpecified.ql b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndexCodeSpecified.ql new file mode 100644 index 00000000000..e079aef2413 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndexCodeSpecified.ql @@ -0,0 +1,58 @@ +/** + * @name Improper validation of code-specified array index + * @description Using a code-specified value as an index to an array, without + * proper validation, can lead to index out of bound exceptions. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/improper-validation-of-array-index-code-specified + * @tags security + * external/cwe/cwe-129 + */ + +import java +import ArraySizing +import BoundingChecks + +class BoundedFlowSourceConf extends DataFlow::Configuration { + BoundedFlowSourceConf() { this = "BoundedFlowSource" } + override predicate isSource(DataFlow::Node source) { + source instanceof BoundedFlowSource + } + override predicate isSink(DataFlow::Node sink) { + exists(CheckableArrayAccess arrayAccess | + arrayAccess.canThrowOutOfBounds(sink.asExpr()) + ) + } +} + +from BoundedFlowSource source, Expr index, CheckableArrayAccess arrayAccess +where + arrayAccess.canThrowOutOfBounds(index) and + any(BoundedFlowSourceConf conf).hasFlow(source, DataFlow::exprNode(index)) and + source != DataFlow::exprNode(index) and + not ( + ( + // The input has a lower bound. + source.lowerBound() >= 0 or + // There is a condition dominating this expression ensuring that the index is >= 0. + lowerBound(arrayAccess.getIndexExpr()) >= 0 + ) + and + ( + // The input has an upper bound, and the array has a fixed size, and that fixed size is less. + source.upperBound() < fixedArraySize(arrayAccess) or + // There is a condition dominating this expression that ensures the index is less than the length. + lessthanLength(arrayAccess) + ) + ) + and + /* + * Exclude cases where the array is assigned multiple times. The checks for bounded flow sources + * can use fixed sizes for arrays, but this doesn't work well when the array is initialized to zero + * and subsequently reassigned or grown. + */ + count(arrayAccess.getArray().(VarAccess).getVariable().getAnAssignedValue()) = 1 +select arrayAccess.getIndexExpr(), + "$@ flows to the index used in this array access, and may cause the operation to throw an ArrayIndexOutOfBoundsException.", + source, source.getDescription() diff --git a/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndexLocal.qhelp b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndexLocal.qhelp new file mode 100644 index 00000000000..554a27b1fdc --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndexLocal.qhelp @@ -0,0 +1,5 @@ + + + diff --git a/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndexLocal.ql b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndexLocal.ql new file mode 100644 index 00000000000..00a04b930e6 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-129/ImproperValidationOfArrayIndexLocal.ql @@ -0,0 +1,31 @@ +/** + * @name Improper validation of local user-provided array index + * @description Using local user input as an index to an array, without + * proper validation, can lead to index out of bound exceptions. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/improper-validation-of-array-index-local + * @tags security + * external/cwe/cwe-129 + */ + +import java +import ArraySizing +import semmle.code.java.dataflow.FlowSources + +class Conf extends TaintTracking::Configuration { + Conf() { this = "LocalUserInputTocanThrowOutOfBoundsDueToEmptyArrayConfig" } + override predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput } + override predicate isSink(DataFlow::Node sink) { + any(CheckableArrayAccess caa).canThrowOutOfBounds(sink.asExpr()) + } +} + +from LocalUserInput source, Expr index, CheckableArrayAccess arrayAccess +where + arrayAccess.canThrowOutOfBounds(index) and + any(Conf conf).hasFlow(source, DataFlow::exprNode(index)) +select arrayAccess.getIndexExpr(), + "$@ flows to here and is used as an index causing an ArrayIndexOutOfBoundsException.", + source, "User-provided value" diff --git a/java/ql/src/Security/CWE/CWE-134/ExternallyControlledFormatString.java b/java/ql/src/Security/CWE/CWE-134/ExternallyControlledFormatString.java new file mode 100644 index 00000000000..01321ffa1de --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-134/ExternallyControlledFormatString.java @@ -0,0 +1,27 @@ +public class ResponseSplitting extends HttpServlet { + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + Calendar expirationDate = new GregorianCalendar(2017, GregorianCalendar.SEPTEMBER, 1); + // User provided value + String cardSecurityCode = request.getParameter("cardSecurityCode"); + + if (notValid(cardSecurityCode)) { + + /* + * BAD: user provided value is included in the format string. + * A malicious user could provide an extra format specifier, which causes an + * exception to be thrown. Or they could provide a %1$tm or %1$te format specifier to + * access the month or day of the expiration date. + */ + System.out.format(cardSecurityCode + + " is not the right value. Hint: the card expires in %1$ty.", + expirationDate); + + // GOOD: %s is used to include the user-provided cardSecurityCode in the output + System.out.format("%s is not the right value. Hint: the card expires in %2$ty.", + cardSecurityCode, + expirationDate); + } + + } +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-134/ExternallyControlledFormatString.qhelp b/java/ql/src/Security/CWE/CWE-134/ExternallyControlledFormatString.qhelp new file mode 100644 index 00000000000..d6dd3ada706 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-134/ExternallyControlledFormatString.qhelp @@ -0,0 +1,49 @@ + + + +

    The String.format method and related methods, like PrintStream.printf and +Formatter.format, all accept a format string that is used to format the trailing +arguments to the format call by providing inline format specifiers. If the format string contains +unsanitized input from an untrusted source, then that string may contain extra format specifiers that +cause an exception to be thrown or information to be leaked.

    + +

    The Java standard library implementation for the format methods throws an exception if either the +format specifier does not match the type of the argument, or if there are too few or too many +arguments. If unsanitized input is used in the format string, it may contain invalid extra format +specifiers which cause an exception to be thrown.

    + +

    Positional format specifiers may be used to access an argument to the format call by position. +Unsanitized input in the format string may use a positional format specifier to access information +that was not intended to be visible. For example, when formatting a Calendar instance we may intend +to print only the year, but a user-specified format string may include a specifier to access the month +and day.

    + +
    + +

    If the argument passed as a format string is meant to be a plain string rather than a format string, +then pass %s as the format string, and pass the original argument as the sole trailing +argument.

    + +
    + +

    The following program is meant to check a card security code for a stored credit card:

    + +

    However, in the first format call it uses the cardSecurityCode provided by the user in a format +string. If the user includes a format specifier in the cardSecurityCode field, they may be able to +cause an exception to be thrown, or to be able to access extra information about the stored card +expiration date.

    +

    The second format call shows the correct approach. The user-provided value is passed as an +argument to the format call. This prevents any format specifiers in the user provided value from +being evaluated.

    +
    + + + +
  • CERT Java Coding Standard: IDS06-J. Exclude unsanitized user input from format strings.
  • +
  • Java SE Documentation: Formatting Numeric Print Output.
  • +
  • Java API: Formatter.
  • + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-134/ExternallyControlledFormatString.ql b/java/ql/src/Security/CWE/CWE-134/ExternallyControlledFormatString.ql new file mode 100644 index 00000000000..e4999133413 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-134/ExternallyControlledFormatString.ql @@ -0,0 +1,28 @@ +/** + * @name Use of externally-controlled format string + * @description Using external input in format strings can lead to exceptions or information leaks. + * @kind problem + * @problem.severity error + * @precision high + * @id java/tainted-format-string + * @tags security + * external/cwe/cwe-134 + */ + +import java +import semmle.code.java.dataflow.FlowSources +import semmle.code.java.StringFormat + +class ExternallyControlledFormatStringConfig extends TaintTracking::Configuration { + ExternallyControlledFormatStringConfig() { this = "ExternallyControlledFormatStringConfig" } + override predicate isSource(DataFlow::Node source) { source instanceof RemoteUserInput } + override predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(StringFormat formatCall).getFormatArgument() } + override predicate isSanitizer(DataFlow::Node node) { node.getType() instanceof NumericType or node.getType() instanceof BooleanType } +} + +from RemoteUserInput source, StringFormat formatCall, ExternallyControlledFormatStringConfig conf +where + conf.hasFlow(source, DataFlow::exprNode(formatCall.getFormatArgument())) +select formatCall.getFormatArgument(), + "$@ flows to here and is used in a format string.", + source, "User-provided value" diff --git a/java/ql/src/Security/CWE/CWE-134/ExternallyControlledFormatStringLocal.qhelp b/java/ql/src/Security/CWE/CWE-134/ExternallyControlledFormatStringLocal.qhelp new file mode 100644 index 00000000000..cb33b0d27ba --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-134/ExternallyControlledFormatStringLocal.qhelp @@ -0,0 +1,5 @@ + + + diff --git a/java/ql/src/Security/CWE/CWE-134/ExternallyControlledFormatStringLocal.ql b/java/ql/src/Security/CWE/CWE-134/ExternallyControlledFormatStringLocal.ql new file mode 100644 index 00000000000..7e5d8704526 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-134/ExternallyControlledFormatStringLocal.ql @@ -0,0 +1,27 @@ +/** + * @name Use of externally-controlled format string from local source + * @description Using external input in format strings can lead to exceptions or information leaks. + * @kind path-problem + * @problem.severity recommendation + * @precision medium + * @id java/tainted-format-string-local + * @tags security + * external/cwe/cwe-134 + */ + +import java +import semmle.code.java.dataflow.FlowSources +import semmle.code.java.StringFormat + +import DataFlow::PathGraph + +class ExternallyControlledFormatStringLocalConfig extends TaintTracking::Configuration { + ExternallyControlledFormatStringLocalConfig() { this = "ExternallyControlledFormatStringLocalConfig" } + override predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput } + override predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(StringFormat formatCall).getFormatArgument() } +} + +from DataFlow::PathNode source, DataFlow::PathNode sink, StringFormat formatCall, ExternallyControlledFormatStringLocalConfig conf +where conf.hasFlowPath(source, sink) and sink.getNode().asExpr() = formatCall.getFormatArgument() +select formatCall.getFormatArgument(), source, sink, "$@ flows to here and is used in a format string.", + source.getNode(), "User-provided value" diff --git a/java/ql/src/Security/CWE/CWE-190/ArithmeticCommon.qll b/java/ql/src/Security/CWE/CWE-190/ArithmeticCommon.qll new file mode 100644 index 00000000000..57c8208c6d8 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-190/ArithmeticCommon.qll @@ -0,0 +1,115 @@ +import semmle.code.java.arithmetic.Overflow +import semmle.code.java.controlflow.Dominance +import semmle.code.java.dataflow.DefUse +import semmle.code.java.controlflow.Guards + +/* + * The type of `exp` is narrower than or equal to `numType`, + * or there is an enclosing cast to a type at least as narrow as 'numType'. + */ +predicate narrowerThanOrEqualTo(ArithExpr exp, NumType numType) { + exp.getType().(NumType).widerThan(numType) + implies + exists(CastExpr cast | cast.getAChildExpr().getProperExpr() = exp | + numType.widerThanOrEqualTo((NumType)cast.getType()) + ) +} + +/** Holds if the size of this use is guarded using `Math.abs`. */ +predicate guardedAbs(ArithExpr e, Expr use) { + exists(MethodAccess m | + m.getMethod() instanceof MethodAbs + | + m.getArgument(0) = use and + guardedLesser(e, m) + ) +} + +/** Holds if the size of this use is guarded to be less than something. */ +predicate guardedLesser(ArithExpr e, Expr use) { + exists(ConditionBlock c, ComparisonExpr guard | + use = guard.getLesserOperand() and + guard = c.getCondition() and + c.controls(e.getBasicBlock(), true) + ) or + guardedAbs(e, use) +} + +/** Holds if the size of this use is guarded to be greater than something. */ +predicate guardedGreater(ArithExpr e, Expr use) { + exists(ConditionBlock c, ComparisonExpr guard | + use = guard.getGreaterOperand() and + guard = c.getCondition() and + c.controls(e.getBasicBlock(), true) + ) or + guardedAbs(e, use) +} + +/** Holds if this expression is (crudely) guarded by `use`. */ +predicate guarded(ArithExpr e, Expr use) { + exists(ConditionBlock c, ComparisonExpr guard | + use = guard.getAnOperand() and + guard = c.getCondition() and + c.controls(e.getBasicBlock(), true) + ) +} + +/** A prior use of the same variable that could see the same value. */ +VarAccess priorAccess(VarAccess access) { + useUsePair(result, access) +} + +/** Holds if `e` is guarded against overflow by `use`. */ +predicate guardedAgainstOverflow(ArithExpr e, VarAccess use) { + use = e.getAnOperand() and + ( + // overflow possible if large + e instanceof AddExpr and guardedLesser(e, priorAccess(use)) or + e instanceof PreIncExpr and guardedLesser(e, priorAccess(use)) or + e instanceof PostIncExpr and guardedLesser(e, priorAccess(use)) or + // overflow unlikely with subtraction + e instanceof SubExpr or + e instanceof PreDecExpr or + e instanceof PostDecExpr or + // overflow possible if large or small + e instanceof MulExpr and guardedLesser(e, priorAccess(use)) and + guardedGreater(e, priorAccess(use)) or + // overflow possible if MIN_VALUE + e instanceof DivExpr and guardedGreater(e, priorAccess(use)) + ) +} + +/** Holds if `e` is guarded against underflow by `use`. */ +predicate guardedAgainstUnderflow(ArithExpr e, VarAccess use) { + use = e.getAnOperand() and + ( + // underflow unlikely for addition + e instanceof AddExpr or + e instanceof PreIncExpr or + e instanceof PostIncExpr or + // underflow possible if use is left operand and small + e instanceof SubExpr and (use = e.getRightOperand() or guardedGreater(e, priorAccess(use))) or + e instanceof PreDecExpr and guardedGreater(e, priorAccess(use)) or + e instanceof PostDecExpr and guardedGreater(e, priorAccess(use)) or + // underflow possible if large or small + e instanceof MulExpr and guardedLesser(e, priorAccess(use)) and + guardedGreater(e, priorAccess(use)) or + // underflow possible if MAX_VALUE + e instanceof DivExpr and guardedLesser(e, priorAccess(use)) + ) +} + +/** Holds if the result of `exp` has certain bits filtered by a bitwise and. */ +private predicate inBitwiseAnd(Expr exp) { + exists(AndBitwiseExpr a | a.getAnOperand() = exp) or + inBitwiseAnd(exp.(ParExpr).getExpr()) or + inBitwiseAnd(exp.(LShiftExpr).getAnOperand()) or + inBitwiseAnd(exp.(RShiftExpr).getAnOperand()) or + inBitwiseAnd(exp.(URShiftExpr).getAnOperand()) +} + +/** Holds if overflow/underflow is irrelevant for this expression. */ +predicate overflowIrrelevant(ArithExpr exp) { + inBitwiseAnd(exp) or + exp.getEnclosingCallable() instanceof HashCodeMethod +} diff --git a/java/ql/src/Security/CWE/CWE-190/ArithmeticTainted.java b/java/ql/src/Security/CWE/CWE-190/ArithmeticTainted.java new file mode 100644 index 00000000000..844a35a85f8 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-190/ArithmeticTainted.java @@ -0,0 +1,29 @@ +class Test { + public static void main(String[] args) { + { + int data; + + BufferedReader readerBuffered = new BufferedReader( + new InputStreamReader(System.in, "UTF-8")); + String stringNumber = readerBuffered.readLine(); + if (stringNumber != null) { + data = Integer.parseInt(stringNumber.trim()); + } else { + data = 0; + } + + // BAD: may overflow if input data is very large, for example + // 'Integer.MAX_VALUE' + int scaled = data * 10; + + //... + + // GOOD: use a guard to ensure no overflows occur + int scaled2; + if (data < Integer.MAX_VALUE / 10) + scaled2 = data * 10; + else + scaled2 = Integer.MAX_VALUE; + } + } +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-190/ArithmeticTainted.qhelp b/java/ql/src/Security/CWE/CWE-190/ArithmeticTainted.qhelp new file mode 100644 index 00000000000..93453d10645 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-190/ArithmeticTainted.qhelp @@ -0,0 +1,47 @@ + + + +

    Performing calculations on user-controlled data can result in integer overflows +unless the input is validated.

    + +

    If the user is free to enter very large numbers, even arithmetic operations that would usually +result in a small change in magnitude may result in overflows.

    + +
    + + +

    Always guard against overflow in arithmetic operations on user-controlled data by doing one of the +following:

    + +
      +
    • Validate the user input.
    • +
    • Define a guard on the arithmetic expression, so that the operation is performed only if the +result can be known to be less than, or equal to, the maximum value for the type, for example MAX_VALUE.
    • +
    • Use a wider type, so that larger input values do not cause overflow.
    • +
    + +
    + + +

    In this example, a value is read from standard input into an int. Because the value +is a user-controlled value, it could be extremely large. Performing arithmetic operations on this +value could therefore cause an overflow. To avoid this happening, the example shows how to perform +a check before performing a multiplication.

    + + + +
    + + +
  • The CERT Oracle Secure Coding Standard for Java: + NUM00-J. Detect or prevent integer overflow.
  • + + + + + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-190/ArithmeticTainted.ql b/java/ql/src/Security/CWE/CWE-190/ArithmeticTainted.ql new file mode 100644 index 00000000000..bf65cf9c3f2 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-190/ArithmeticTainted.ql @@ -0,0 +1,41 @@ +/** + * @name User-controlled data in arithmetic expression + * @description Arithmetic operations on user-controlled data that is not validated can cause + * overflows. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/tainted-arithmetic + * @tags security + * external/cwe/cwe-190 + * external/cwe/cwe-191 + */ +import java +import semmle.code.java.dataflow.FlowSources +import ArithmeticCommon + +predicate sink(ArithExpr exp, VarAccess tainted, string effect) { + exp.getAnOperand() = tainted and + ( + not guardedAgainstUnderflow(exp, tainted) and effect = "underflow" or + not guardedAgainstOverflow(exp, tainted) and effect = "overflow" + ) and + // Exclude widening conversions of tainted values due to binary numeric promotion (JLS 5.6.2) + // unless there is an enclosing cast down to a narrower type. + narrowerThanOrEqualTo(exp, tainted.getType()) and + not overflowIrrelevant(exp) +} + +class RemoteUserInputConfig extends TaintTracking::Configuration { + RemoteUserInputConfig() { this = "ArithmeticTainted.ql:RemoteUserInputConfig" } + override predicate isSource(DataFlow::Node source) { source instanceof RemoteUserInput } + override predicate isSink(DataFlow::Node sink) { sink(_, sink.asExpr(), _) } + override predicate isSanitizer(DataFlow::Node n) { n.getType() instanceof BooleanType } +} + +from ArithExpr exp, VarAccess tainted, RemoteUserInput origin, string effect, RemoteUserInputConfig conf +where + conf.hasFlow(origin, DataFlow::exprNode(tainted)) and + sink(exp, tainted, effect) +select exp, "$@ flows to here and is used in arithmetic, potentially causing an " + effect + ".", + origin, "User-provided value" diff --git a/java/ql/src/Security/CWE/CWE-190/ArithmeticTaintedLocal.qhelp b/java/ql/src/Security/CWE/CWE-190/ArithmeticTaintedLocal.qhelp new file mode 100644 index 00000000000..5a193f13666 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-190/ArithmeticTaintedLocal.qhelp @@ -0,0 +1,5 @@ + + + diff --git a/java/ql/src/Security/CWE/CWE-190/ArithmeticTaintedLocal.ql b/java/ql/src/Security/CWE/CWE-190/ArithmeticTaintedLocal.ql new file mode 100644 index 00000000000..fc4ab7c7f27 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-190/ArithmeticTaintedLocal.ql @@ -0,0 +1,41 @@ +/** + * @name Local-user-controlled data in arithmetic expression + * @description Arithmetic operations on user-controlled data that is not validated can cause + * overflows. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/tainted-arithmetic-local + * @tags security + * external/cwe/cwe-190 + * external/cwe/cwe-191 + */ +import java +import semmle.code.java.dataflow.FlowSources +import ArithmeticCommon + +predicate sink(ArithExpr exp, VarAccess tainted, string effect) { + exp.getAnOperand() = tainted and + ( + not guardedAgainstUnderflow(exp, tainted) and effect = "underflow" or + not guardedAgainstOverflow(exp, tainted) and effect = "overflow" + ) and + // Exclude widening conversions of tainted values due to binary numeric promotion (JLS 5.6.2) + // unless there is an enclosing cast down to a narrower type. + narrowerThanOrEqualTo(exp, tainted.getType()) and + not overflowIrrelevant(exp) +} + +class ArithmeticTaintedLocalFlowConfig extends TaintTracking::Configuration { + ArithmeticTaintedLocalFlowConfig() { this = "ArithmeticTaintedLocalFlowConfig" } + override predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput } + override predicate isSink(DataFlow::Node sink) { sink(_, sink.asExpr(), _) } + override predicate isSanitizer(DataFlow::Node n) { n.getType() instanceof BooleanType } +} + +from ArithExpr exp, VarAccess tainted, LocalUserInput origin, string effect +where + any(ArithmeticTaintedLocalFlowConfig conf).hasFlow(origin, DataFlow::exprNode(tainted)) and + sink(exp, tainted, effect) +select exp, "$@ flows to here and is used in arithmetic, potentially causing an " + effect + ".", + origin, "User-provided value" diff --git a/java/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.java b/java/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.java new file mode 100644 index 00000000000..14a6973458b --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.java @@ -0,0 +1,19 @@ +class Test { + public static void main(String[] args) { + { + int data = (new java.security.SecureRandom()).nextInt(); + + // BAD: may overflow if data is large + int scaled = data * 10; + + // ... + + // GOOD: use a guard to ensure no overflows occur + int scaled2; + if (data < Integer.MAX_VALUE/10) + scaled2 = data * 10; + else + scaled2 = Integer.MAX_VALUE; + } + } +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.qhelp b/java/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.qhelp new file mode 100644 index 00000000000..537a32f5dec --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.qhelp @@ -0,0 +1,47 @@ + + + +

    Performing calculations on uncontrolled data can result in integer overflows +unless the input is validated.

    + +

    If the data is not under your control, and can take extremely large values, +even arithmetic operations that would usually result in a small change in magnitude may result in overflows.

    + +
    + + +

    Always guard against overflow in arithmetic operations on uncontrolled data by doing one of the +following:

    + +
      +
    • Validate the data.
    • +
    • Define a guard on the arithmetic expression, so that the operation is performed only if the +result can be known to be less than, or equal to, the maximum value for the type, for example MAX_VALUE.
    • +
    • Use a wider type, so that larger input values do not cause overflow.
    • +
    + +
    + + +

    In this example, a random integer is generated. Because the value +is not controlled by the programmer, it could be extremely large. Performing arithmetic operations on this +value could therefore cause an overflow. To avoid this happening, the example shows how to perform +a check before performing a multiplication.

    + + + +
    + + +
  • The CERT Oracle Secure Coding Standard for Java: + NUM00-J. Detect or prevent integer overflow.
  • + + + + + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.ql b/java/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.ql new file mode 100644 index 00000000000..da6f0ae1bec --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.ql @@ -0,0 +1,69 @@ +/** + * @name Uncontrolled data in arithmetic expression + * @description Arithmetic operations on uncontrolled data that is not validated can cause + * overflows. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/uncontrolled-arithmetic + * @tags security + * external/cwe/cwe-190 + * external/cwe/cwe-191 + */ +import java +import semmle.code.java.dataflow.TaintTracking +import semmle.code.java.security.SecurityTests +import ArithmeticCommon + +class TaintSource extends DataFlow::ExprNode { + TaintSource() { + // Either this is an access to a random number generating method of the right kind, ... + exists(Method def | + def = this.getExpr().(MethodAccess).getMethod() and + ( + // Some random-number methods are omitted: + // `nextDouble` and `nextFloat` are between 0 and 1, + // `nextGaussian` is extremely unlikely to hit max values. + def.getName() = "nextInt" or + def.getName() = "nextLong" + ) and + def.getNumberOfParameters() = 0 and + def.getDeclaringType().hasQualifiedName("java.util", "Random") + ) or + // ... or this is the array parameter of `nextBytes`, which is filled with random bytes. + exists(MethodAccess m, Method def | + m.getAnArgument() = this.getExpr() and + m.getMethod() = def and + def.getName() = "nextBytes" and + def.getNumberOfParameters() = 1 and + def.getDeclaringType().hasQualifiedName("java.util", "Random") + ) + } +} + +predicate sink(ArithExpr exp, VarAccess tainted, string effect) { + exp.getAnOperand() = tainted and + ( + not guardedAgainstUnderflow(exp, tainted) and effect = "underflow" or + not guardedAgainstOverflow(exp, tainted) and effect = "overflow" + ) and + // Exclude widening conversions of tainted values due to binary numeric promotion (JLS 5.6.2) + // unless there is an enclosing cast down to a narrower type. + narrowerThanOrEqualTo(exp, tainted.getType()) and + not overflowIrrelevant(exp) and + not exp.getEnclosingCallable().getDeclaringType() instanceof NonSecurityTestClass +} + +class ArithmeticUncontrolledFlowConfig extends TaintTracking::Configuration { + ArithmeticUncontrolledFlowConfig() { this = "ArithmeticUncontrolledFlowConfig" } + override predicate isSource(DataFlow::Node source) { source instanceof TaintSource } + override predicate isSink(DataFlow::Node sink) { sink(_, sink.asExpr(), _) } + override predicate isSanitizer(DataFlow::Node n) { n.getType() instanceof BooleanType } +} + +from ArithExpr exp, VarAccess tainted, TaintSource origin, string effect, ArithmeticUncontrolledFlowConfig conf +where + conf.hasFlow(origin, DataFlow::exprNode(tainted)) and + sink(exp, tainted, effect) +select exp, "$@ flows to here and is used in arithmetic, potentially causing an " + effect + ".", + origin, "Uncontrolled value" diff --git a/java/ql/src/Security/CWE/CWE-190/ArithmeticWithExtremeValues.java b/java/ql/src/Security/CWE/CWE-190/ArithmeticWithExtremeValues.java new file mode 100644 index 00000000000..b8ad556570f --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-190/ArithmeticWithExtremeValues.java @@ -0,0 +1,15 @@ +class Test { + public static void main(String[] args) { + { + long i = Long.MAX_VALUE; + // BAD: overflow + long j = i + 1; + } + + { + int i = Integer.MAX_VALUE; + // GOOD: no overflow + long j = (long)i + 1; + } + } +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-190/ArithmeticWithExtremeValues.qhelp b/java/ql/src/Security/CWE/CWE-190/ArithmeticWithExtremeValues.qhelp new file mode 100644 index 00000000000..e9c18737f4d --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-190/ArithmeticWithExtremeValues.qhelp @@ -0,0 +1,36 @@ + + + +

    Assigning the maximum or minimum value for a type to a variable of that type and then using the +variable in calculations may cause overflows.

    + +
    + + +

    Before using the variable, ensure that it is reassigned a value that does not cause an overflow, +or use a wider type to do the arithmetic.

    + +
    + + +

    In this example, assigning Long.MAX_VALUE to a variable and adding one causes +an overflow. However, casting to a long beforehand ensures that the arithmetic +is done in the wider type, and so does not overflow.

    + + + +
    + + +
  • The CERT Oracle Secure Coding Standard for Java: + NUM00-J. Detect or prevent integer overflow.
  • + + + + + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-190/ArithmeticWithExtremeValues.ql b/java/ql/src/Security/CWE/CWE-190/ArithmeticWithExtremeValues.ql new file mode 100644 index 00000000000..d643bfdabc8 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-190/ArithmeticWithExtremeValues.ql @@ -0,0 +1,87 @@ +/** + * @name Use of extreme values in arithmetic expression + * @description If a variable is assigned the maximum or minimum value for that variable's type and + * is then used in an arithmetic expression, this may result in an overflow. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/extreme-value-arithmetic + * @tags security + * reliability + * external/cwe/cwe-190 + * external/cwe/cwe-191 + */ +import java +import semmle.code.java.dataflow.DataFlow +import ArithmeticCommon + +abstract class ExtremeValueField extends Field { + ExtremeValueField() { + getType() instanceof IntegralType + } +} + +class MinValueField extends ExtremeValueField { + MinValueField() { + this.getName() = "MIN_VALUE" + } +} + +class MaxValueField extends ExtremeValueField { + MaxValueField() { + this.getName() = "MAX_VALUE" + } +} + +class ExtremeSource extends VarAccess { + ExtremeSource() { + this.getVariable() instanceof ExtremeValueField + } +} + +class ExtremeSourceFlowConfig extends DataFlow::Configuration { + ExtremeSourceFlowConfig() { this = "ExtremeSourceFlowConfig" } + override predicate isSource(DataFlow::Node source) { source.asExpr() instanceof ExtremeSource } + override predicate isSink(DataFlow::Node sink) { sink(_, sink.asExpr()) } + override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) { + isSource(node1) and isSource(node2) + } + override predicate isBarrier(DataFlow::Node n) { n.getType() instanceof BooleanType } +} + +predicate sink(ArithExpr exp, VarAccess use) { + use = exp.getAnOperand() and + ( + not guardedAgainstUnderflow(exp, use) or + not guardedAgainstOverflow(exp, use) + ) and + not overflowIrrelevant(exp) and + not exp instanceof DivExpr +} + +predicate query(ArithExpr exp, Variable v, ExtremeValueField f, VarAccess use, ExtremeSource s, Type t) { + // `use` is the use of `v` in `exp`. + use = exp.getAnOperand() and + use = v.getAnAccess() and + // An extreme field flows to `use`. + f = s.getVariable() and + any(ExtremeSourceFlowConfig conf).hasFlow(DataFlow::exprNode(s), DataFlow::exprNode(use)) and + t = s.getType() and + // Division isn't a problem in this case. + not exp instanceof DivExpr +} + +from ArithExpr exp, Variable v, ExtremeValueField f, VarAccess use, ExtremeSource s, string effect, Type t +where + query(exp, v, f, use, s, t) and + // We're not guarded against the appropriate kind of flow error. + ( + f instanceof MinValueField and not guardedAgainstUnderflow(exp, use) and effect = "underflow" or + f instanceof MaxValueField and not guardedAgainstOverflow(exp, use) and effect = "overflow" + ) and + // Exclude widening conversions of extreme values due to binary numeric promotion (JLS 5.6.2) + // unless there is an enclosing cast down to a narrower type. + narrowerThanOrEqualTo(exp, t) and + not overflowIrrelevant(exp) +select exp, "Variable " + v.getName() + " is assigned an extreme value $@, and may cause an " + effect + ".", + s, f.getName() diff --git a/java/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.java b/java/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.java new file mode 100644 index 00000000000..cf9580c6f08 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.java @@ -0,0 +1,36 @@ +class Test { + public static void main(String[] args) { + + { + int BIGNUM = Integer.MAX_VALUE; + long MAXGET = Short.MAX_VALUE + 1; + + char[] buf = new char[BIGNUM]; + + short bytesReceived = 0; + + // BAD: 'bytesReceived' is compared with a value of wider type. + // 'bytesReceived' overflows before reaching MAXGET, + // causing an infinite loop. + while (bytesReceived < MAXGET) { + bytesReceived += getFromInput(buf, bytesReceived); + } + } + + { + long bytesReceived2 = 0; + + // GOOD: 'bytesReceived2' has a type at least as wide as MAXGET. + while (bytesReceived2 < MAXGET) { + bytesReceived2 += getFromInput(buf, bytesReceived2); + } + } + + } + + public static int getFromInput(char[] buf, short pos) { + // write to buf + // ... + return 1; + } +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.qhelp b/java/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.qhelp new file mode 100644 index 00000000000..537b20d4c2c --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.qhelp @@ -0,0 +1,42 @@ + + + +

    In a loop condition, comparison of a value of a narrow type with a value of a wide type may +always evaluate to true if the wider value is sufficiently large (or small). This is +because the narrower value may overflow. This can lead to an infinite loop.

    + +
    + + +

    Change the types of the compared values so that the value on the narrower side of the +comparison is at least as wide as the value it is being compared with.

    + +
    + + +

    In this example, bytesReceived is compared against MAXGET in a +while loop. However, bytesReceived is a short, and +MAXGET is a long. Because MAXGET is larger than +Short.MAX_VALUE, the loop condition is always true, so the loop never +terminates.

    + +

    This problem is avoided in the 'GOOD' case because bytesReceived2 is a +long, which is as wide as the type of MAXGET.

    + + + +
    + + +
  • The CERT Oracle Secure Coding Standard for Java: + NUM00-J. Detect or prevent integer overflow.
  • + + + + + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.ql b/java/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.ql new file mode 100644 index 00000000000..1dc9e5a7a84 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.ql @@ -0,0 +1,70 @@ +/** + * @name Comparison of narrow type with wide type in loop condition + * @description Comparisons between types of different widths in a loop condition can cause the loop + * to behave unexpectedly. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/comparison-with-wider-type + * @tags reliability + * security + * external/cwe/cwe-190 + * external/cwe/cwe-197 + */ +import java +import semmle.code.java.arithmetic.Overflow + +int leftWidth(ComparisonExpr e) { + result = e.getLeftOperand().getType().(NumType).getWidthRank() +} + +int rightWidth(ComparisonExpr e) { + result = e.getRightOperand().getType().(NumType).getWidthRank() +} + +abstract class WideningComparison extends BinaryExpr { + WideningComparison() { + this instanceof ComparisonExpr + } + abstract Expr getNarrower(); + abstract Expr getWider(); +} + +class LTWideningComparison extends WideningComparison { + LTWideningComparison() { + (this instanceof LEExpr or this instanceof LTExpr) and + leftWidth(this) < rightWidth(this) + } + + override Expr getNarrower() { + result = getLeftOperand() + } + + override Expr getWider() { + result = getRightOperand() + } +} + +class GTWideningComparison extends WideningComparison { + GTWideningComparison() { + (this instanceof GEExpr or this instanceof GTExpr) and + leftWidth(this) > rightWidth(this) + } + + override Expr getNarrower() { + result = getRightOperand() + } + + override Expr getWider() { + result = getLeftOperand() + } +} + +from WideningComparison c, LoopStmt l +where + not c.getAnOperand().isCompileTimeConstant() and + l.getCondition().getAChildExpr*() = c +select c, "Comparison between $@ of type " + c.getNarrower().getType().getName() + + " and $@ of wider type " + c.getWider().getType().getName() + ".", + c.getNarrower(), "expression", + c.getWider(), "expression" diff --git a/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.java b/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.java new file mode 100644 index 00000000000..8a13edaea4e --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.java @@ -0,0 +1,20 @@ +protected void doGet(HttpServletRequest request, HttpServletResponse response) { + try { + doSomeWork(); + } catch (NullPointerException ex) { + // BAD: printing a stack trace back to the response + ex.printStackTrace(response.getWriter()); + return; + } + + try { + doSomeWork(); + } catch (NullPointerException ex) { + // GOOD: log the stack trace, and send back a non-revealing response + log("Exception occurred", ex); + response.sendError( + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + "Exception occurred"); + return; + } +} diff --git a/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.qhelp b/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.qhelp new file mode 100644 index 00000000000..b772b7e3c57 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.qhelp @@ -0,0 +1,47 @@ + + + +

    Software developers often add stack traces to error messages, as a +debugging aid. Whenever that error message occurs for an end user, the +developer can use the stack trace to help identify how to fix the +problem. In particular, stack traces can tell the developer more about +the sequence of events that led to a failure, as opposed to merely the +final state of the software when the error occurred.

    + +

    Unfortunately, the same information can be useful to an attacker. +The sequence of class names in a stack trace can reveal the structure +of the application as well as any internal components it relies on. +Furthermore, the error message at the top of a stack trace can include +information such as server-side file names and SQL code that the +application relies on, allowing an attacker to fine-tune a subsequent +injection attack.

    +
    + + +

    Send the user a more generic error message that reveals less information. +Either suppress the stack trace entirely, or log it only on the server.

    +
    + + +

    In the following example, an exception is handled in two different +ways. In the first version, labeled BAD, the exception is sent back to +the remote user using the sendError() method. As such, +the user is able to see a detailed stack trace, which may contain +sensitive information. In the second version, the error message is +logged only on the server. That way, the developers can still access +and use the error log, but remote users will not see the +information.

    + + +
    + + +
  • OWASP: Information Leak.
  • + +
  • CERT Java Coding Standard: +ERR01-J. +Do not allow exceptions to expose sensitive information.
  • +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.ql b/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.ql new file mode 100644 index 00000000000..81a47072da5 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.ql @@ -0,0 +1,125 @@ +/** + * @name Information exposure through a stack trace + * @description Information from a stack trace propagates to an external user. + * Stack traces can unintentionally reveal implementation details + * that are useful to an attacker for developing a subsequent exploit. + * @kind problem + * @problem.severity error + * @precision high + * @id java/stack-trace-exposure + * @tags security + * external/cwe/cwe-209 + * external/cwe/cwe-497 + */ + +import java +import semmle.code.java.dataflow.DataFlow +import semmle.code.java.security.XSS + +/** + * One of the `printStackTrace()` overloads on `Throwable`. + */ +class PrintStackTraceMethod extends Method { + PrintStackTraceMethod() { + getDeclaringType().hasQualifiedName("java.lang", "Throwable") and + getName() = "printStackTrace" + } +} + +class ServletWriterSourceToPrintStackTraceMethodFlowConfig extends TaintTracking::Configuration { + ServletWriterSourceToPrintStackTraceMethodFlowConfig() { this = "StackTraceExposure::ServletWriterSourceToPrintStackTraceMethodFlowConfig" } + override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof ServletWriterSource } + override predicate isSink(DataFlow::Node sink) { exists(MethodAccess ma | sink.asExpr() = ma.getAnArgument() and ma.getMethod() instanceof PrintStackTraceMethod) } +} + +/** + * A call that uses `Throwable.printStackTrace()` on a stream that is connected + * to external output. + */ +predicate printsStackToWriter(MethodAccess call) { + exists(ServletWriterSourceToPrintStackTraceMethodFlowConfig writerSource, PrintStackTraceMethod printStackTrace | + call.getMethod() = printStackTrace and + writerSource.hasFlowToExpr(call.getAnArgument()) + ) +} + +/** + * A `PrintWriter` that wraps a given string writer. This pattern is used + * in the most common idiom for converting a `Throwable` to a string. + */ +predicate printWriterOnStringWriter(Expr printWriter, Variable stringWriterVar) { + printWriter.getType().(Class).hasQualifiedName("java.io", "PrintWriter") and + stringWriterVar.getType().(Class).hasQualifiedName("java.io", "StringWriter") and ( + printWriter.(ClassInstanceExpr).getAnArgument() = stringWriterVar.getAnAccess() or + printWriterOnStringWriter(printWriter.(VarAccess).getVariable().getInitializer(), stringWriterVar) + ) +} + +predicate stackTraceExpr(Expr exception, MethodAccess stackTraceString) { + exists(Expr printWriter, Variable stringWriterVar, MethodAccess printStackCall | + printWriterOnStringWriter(printWriter, stringWriterVar) and + printStackCall.getMethod() instanceof PrintStackTraceMethod and + printStackCall.getAnArgument() = printWriter and + printStackCall.getQualifier() = exception and + stackTraceString.getQualifier() = stringWriterVar.getAnAccess() and + stackTraceString.getMethod().getName() = "toString" and + stackTraceString.getMethod().getNumberOfParameters() = 0 + ) +} + +class StackTraceStringToXssSinkFlowConfig extends TaintTracking::Configuration2 { + StackTraceStringToXssSinkFlowConfig() { this = "StackTraceExposure::StackTraceStringToXssSinkFlowConfig" } + override predicate isSource(DataFlow::Node src) { stackTraceExpr(_, src.asExpr()) } + override predicate isSink(DataFlow::Node sink) { sink instanceof XssSink } +} + +/** + * A write of stack trace data to an external stream. + */ +predicate printsStackExternally(MethodAccess call, Expr stackTrace) { + printsStackToWriter(call) and + call.getQualifier() = stackTrace +} + +/** + * A stringified stack trace flows to an external sink. + */ +predicate stringifiedStackFlowsExternally(XssSink externalExpr, Expr stackTrace) { + exists(MethodAccess stackTraceString, StackTraceStringToXssSinkFlowConfig conf | + stackTraceExpr(stackTrace, stackTraceString) and + conf.hasFlow(DataFlow::exprNode(stackTraceString), externalExpr) + ) +} + +class GetMessageFlowSource extends MethodAccess { + GetMessageFlowSource() { + exists(Method method | + method = this.getMethod() and + method.hasName("getMessage") and + method.hasNoParameters() and + method.getDeclaringType().hasQualifiedName("java.lang", "Throwable") + ) + } +} + +class GetMessageFlowSourceToXssSinkFlowConfig extends TaintTracking::Configuration2 { + GetMessageFlowSourceToXssSinkFlowConfig() { this = "StackTraceExposure::GetMessageFlowSourceToXssSinkFlowConfig" } + override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof GetMessageFlowSource } + override predicate isSink(DataFlow::Node sink) { sink instanceof XssSink } +} + +/** + * A call to `getMessage()` that then flows to a servlet response. + */ +predicate getMessageFlowsExternally(XssSink externalExpr, GetMessageFlowSource getMessage) { + any(GetMessageFlowSourceToXssSinkFlowConfig conf).hasFlow(DataFlow::exprNode(getMessage), externalExpr) +} + +from Expr externalExpr, Expr errorInformation +where + printsStackExternally(externalExpr, errorInformation) or + stringifiedStackFlowsExternally(DataFlow::exprNode(externalExpr), errorInformation) or + getMessageFlowsExternally(DataFlow::exprNode(externalExpr), errorInformation) +select + externalExpr, "$@ can be exposed to an external user.", + errorInformation, "Error information" diff --git a/java/ql/src/Security/CWE/CWE-312/CleartextStorage.java b/java/ql/src/Security/CWE/CWE-312/CleartextStorage.java new file mode 100644 index 00000000000..d8a534e439d --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-312/CleartextStorage.java @@ -0,0 +1,28 @@ +public static void main(String[] args) { + { + String data; + PasswordAuthentication credentials = + new PasswordAuthentication("user", "BP@ssw0rd".toCharArray()); + data = credentials.getUserName() + ":" + new String(credentials.getPassword()); + + // BAD: store data in a cookie in cleartext form + response.addCookie(new Cookie("auth", data)); + } + + { + String data; + PasswordAuthentication credentials = + new PasswordAuthentication("user", "GP@ssw0rd".toCharArray()); + String salt = "ThisIsMySalt"; + MessageDigest messageDigest = MessageDigest.getInstance("SHA-512"); + messageDigest.reset(); + String credentialsToHash = + credentials.getUserName() + ":" + credentials.getPassword(); + byte[] hashedCredsAsBytes = + messageDigest.digest((salt+credentialsToHash).getBytes("UTF-8")); + data = bytesToString(hashedCredsAsBytes); + + // GOOD: store data in a cookie in encrypted form + response.addCookie(new Cookie("auth", data)); + } +} diff --git a/java/ql/src/Security/CWE/CWE-312/CleartextStorage.qhelp b/java/ql/src/Security/CWE/CWE-312/CleartextStorage.qhelp new file mode 100644 index 00000000000..ca631a76c3a --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-312/CleartextStorage.qhelp @@ -0,0 +1,43 @@ + + + +

    Sensitive information that is stored unencrypted is accessible to an attacker who gains access to the +storage.

    + +
    + + +

    Ensure that sensitive information is always encrypted before being stored. It may be wise +to encrypt information before it is put into a heap data structure (such as Java.util.Properties) +that may be written to disk later. Objects that are serializable or marshallable should also always contain +encrypted information unless you are certain that they are not ever going to be serialized.

    + +

    In general, decrypt sensitive information only at the point where it is necessary for it to be used in +cleartext.

    + +
    + + +

    The following example shows two ways of storing user credentials in a cookie. In the 'BAD' case, +the credentials are simply stored in cleartext. In the 'GOOD' case, the credentials are hashed before +storing them.

    + + + +
    + + +
  • The CERT Oracle Secure Coding Standard for Java: + SER03-J. Do not serialize unencrypted, sensitive data.
  • +
  • M. Dowd, J. McDonald and J. Schuhm, The Art of Software Security Assessment, 1st Edition, Chapter 2 - 'Common Vulnerabilities of Encryption', p. 43. Addison Wesley, 2006.
  • +
  • M. Howard and D. LeBlanc, Writing Secure Code, 2nd Edition, Chapter 9 - 'Protecting Secret Data', p. 299. Microsoft, 2002.
  • + + + + + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-312/CleartextStorageClass.qhelp b/java/ql/src/Security/CWE/CWE-312/CleartextStorageClass.qhelp new file mode 100644 index 00000000000..884b1dbdd4b --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-312/CleartextStorageClass.qhelp @@ -0,0 +1,5 @@ + + + diff --git a/java/ql/src/Security/CWE/CWE-312/CleartextStorageClass.ql b/java/ql/src/Security/CWE/CWE-312/CleartextStorageClass.ql new file mode 100644 index 00000000000..fc5180e6000 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-312/CleartextStorageClass.ql @@ -0,0 +1,26 @@ +/** + * @name Cleartext storage of sensitive information using storable class + * @description Storing sensitive information in cleartext can expose it to an attacker. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/cleartext-storage-in-class + * @tags security + * external/cwe/cwe-499 + * external/cwe/cwe-312 + */ +import java +import SensitiveStorage + +from SensitiveSource data, ClassStore s, Expr input, Expr store +where + input = s.getAnInput() and + store = s.getAStore() and + data.flowsToCached(input) and + // Exclude results in test code. + not testMethod(store.getEnclosingCallable()) and + not testMethod(data.getEnclosingCallable()) +select store, "Storable class $@ containing $@ is stored here. Data was added $@.", + s, s.toString(), + data, "sensitive data", + input, "here" diff --git a/java/ql/src/Security/CWE/CWE-312/CleartextStorageCookie.qhelp b/java/ql/src/Security/CWE/CWE-312/CleartextStorageCookie.qhelp new file mode 100644 index 00000000000..884b1dbdd4b --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-312/CleartextStorageCookie.qhelp @@ -0,0 +1,5 @@ + + + diff --git a/java/ql/src/Security/CWE/CWE-312/CleartextStorageCookie.ql b/java/ql/src/Security/CWE/CWE-312/CleartextStorageCookie.ql new file mode 100644 index 00000000000..7f5adfe8ca7 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-312/CleartextStorageCookie.ql @@ -0,0 +1,25 @@ +/** + * @name Cleartext storage of sensitive information in cookie + * @description Storing sensitive information in cleartext can expose it to an attacker. + * @kind problem + * @problem.severity error + * @precision high + * @id java/cleartext-storage-in-cookie + * @tags security + * external/cwe/cwe-315 + */ +import java +import SensitiveStorage + +from SensitiveSource data, Cookie s, Expr input, Expr store +where + input = s.getAnInput() and + store = s.getAStore() and + data.flowsToCached(input) and + // Exclude results in test code. + not testMethod(store.getEnclosingCallable()) and + not testMethod(data.getEnclosingCallable()) +select store, "Cookie $@ containing $@ is stored here. Data was added $@.", + s, s.toString(), + data, "sensitive data", + input, "here" diff --git a/java/ql/src/Security/CWE/CWE-312/CleartextStorageProperties.qhelp b/java/ql/src/Security/CWE/CWE-312/CleartextStorageProperties.qhelp new file mode 100644 index 00000000000..884b1dbdd4b --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-312/CleartextStorageProperties.qhelp @@ -0,0 +1,5 @@ + + + diff --git a/java/ql/src/Security/CWE/CWE-312/CleartextStorageProperties.ql b/java/ql/src/Security/CWE/CWE-312/CleartextStorageProperties.ql new file mode 100644 index 00000000000..1246ddbf7e7 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-312/CleartextStorageProperties.ql @@ -0,0 +1,25 @@ +/** + * @name Cleartext storage of sensitive information using 'Properties' class + * @description Storing sensitive information in cleartext can expose it to an attacker. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/cleartext-storage-in-properties + * @tags security + * external/cwe/cwe-313 + */ +import java +import SensitiveStorage + +from SensitiveSource data, Properties s, Expr input, Expr store +where + input = s.getAnInput() and + store = s.getAStore() and + data.flowsToCached(input) and + // Exclude results in test code. + not testMethod(store.getEnclosingCallable()) and + not testMethod(data.getEnclosingCallable()) +select store, "'Properties' class $@ containing $@ is stored here. Data was added $@.", + s, s.toString(), + data, "sensitive data", + input, "here" diff --git a/java/ql/src/Security/CWE/CWE-312/SensitiveStorage.qll b/java/ql/src/Security/CWE/CWE-312/SensitiveStorage.qll new file mode 100644 index 00000000000..c4f0e15626d --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-312/SensitiveStorage.qll @@ -0,0 +1,240 @@ +import java +import semmle.code.java.frameworks.Properties +import semmle.code.java.frameworks.JAXB +import semmle.code.java.dataflow.TaintTracking +import semmle.code.java.dataflow.DataFlow3 +import semmle.code.java.dataflow.DataFlow4 +import semmle.code.java.security.SensitiveActions + +/** Test code filter. */ +predicate testMethod(Method m) { + ( + m instanceof TestMethod + or + m.getDeclaringType() instanceof TestClass + ) and + // Do report results in the Juliet tests. + not m.getLocation().getFile().getAbsolutePath().matches("%CWE%") +} + +private class SensitiveSourceFlowConfig extends TaintTracking::Configuration { + SensitiveSourceFlowConfig() { this = "SensitiveStorage::SensitiveSourceFlowConfig" } + override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SensitiveExpr } + override predicate isSink(DataFlow::Node sink) { + sink.asExpr() = cookieInput(_) or + exists(MethodAccess m | + m.getMethod() instanceof PropertiesSetPropertyMethod and sink.asExpr() = m.getArgument(1) + ) or + sink.asExpr() = getInstanceInput(_, _) + } + override predicate isSanitizer(DataFlow::Node n) { + n.getType() instanceof NumericType or n.getType() instanceof BooleanType + } +} + +/** Class for expressions that may represent 'sensitive' information */ +class SensitiveSource extends Expr { + SensitiveSource() { + // SensitiveExpr is abstract, this lets us inherit from it without + // being a technical subclass + this instanceof SensitiveExpr + } + + /** Holds if this source flows to the `sink`. */ + cached + predicate flowsToCached(Expr sink) { + exists(SensitiveSourceFlowConfig conf | + conf.hasFlow(DataFlow::exprNode(this), DataFlow::exprNode(sink)) + ) + } +} + +/** + * Class representing entities that may be stored/written, with methods + * for finding values that are stored within them, and cases + * of the entity being stored. + */ +abstract class Storable extends ClassInstanceExpr { + /** Gets an "input" that is stored in an instance of this class. */ + abstract Expr getAnInput(); + + /** Gets an expression where an instance of this class is stored (e.g. to disk). */ + abstract Expr getAStore(); +} + +private predicate cookieStore(DataFlow::Node cookie, Expr store) { + exists(MethodAccess m, Method def | + m.getMethod() = def and + def.getName() = "addCookie" and + def.getDeclaringType().getQualifiedName() = "javax.servlet.http.HttpServletResponse" and + store = m and + cookie.asExpr() = m.getAnArgument() + ) +} + +private class CookieToStoreFlowConfig extends DataFlow2::Configuration { + CookieToStoreFlowConfig() { this = "SensitiveStorage::CookieToStoreFlowConfig" } + override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof Cookie } + override predicate isSink(DataFlow::Node sink) { cookieStore(sink, _) } +} + +private Expr cookieInput(Cookie c) { + result = c.getArgument(1) +} + +/** The instantiation of a cookie, which can act as storage. */ +class Cookie extends Storable { + Cookie() { + this.getConstructor().getDeclaringType().getQualifiedName() = "javax.servlet.http.Cookie" + } + + /** Gets an input, for example `input` in `new Cookie("...", input);`. */ + override Expr getAnInput() { + result = cookieInput(this) + } + + /** Gets a store, for example `response.addCookie(cookie);`. */ + override Expr getAStore() { + exists(CookieToStoreFlowConfig conf, DataFlow::Node n | + cookieStore(n, result) and + conf.hasFlow(DataFlow::exprNode(this), n) + ) + } +} + +private predicate propertiesInput(DataFlow::Node prop, Expr input) { + exists(MethodAccess m | + m.getMethod() instanceof PropertiesSetPropertyMethod and + input = m.getArgument(1) and + prop.asExpr() = m.getQualifier() + ) +} + +private predicate propertiesStore(DataFlow::Node prop, Expr store) { + exists(MethodAccess m | + m.getMethod() instanceof PropertiesStoreMethod and + store = m and + prop.asExpr() = m.getQualifier() + ) +} + +private class PropertiesFlowConfig extends DataFlow3::Configuration { + PropertiesFlowConfig() { this = "SensitiveStorage::PropertiesFlowConfig" } + override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof Properties } + override predicate isSink(DataFlow::Node sink) { + propertiesInput(sink, _) or + propertiesStore(sink, _) + } +} + +/** The instantiation of a `Properties` object, which can be stored to disk. */ +class Properties extends Storable { + Properties() { + this.getConstructor().getDeclaringType() instanceof TypeProperty + } + + /** Gets an input, for example `input` in `props.setProperty("password", input);`. */ + override Expr getAnInput() { + exists(PropertiesFlowConfig conf, DataFlow::Node n | + propertiesInput(n, result) and + conf.hasFlow(DataFlow::exprNode(this), n) + ) + } + + /** Gets a store, for example `props.store(outputStream, "...")`. */ + override Expr getAStore() { + exists(PropertiesFlowConfig conf, DataFlow::Node n | + propertiesStore(n, result) and + conf.hasFlow(DataFlow::exprNode(this), n) + ) + } +} + +abstract class ClassStore extends Storable { + /** Gets an input, for example `input` in `instance.password = input`. */ + override Expr getAnInput() { + exists(ClassStoreFlowConfig conf, DataFlow::Node instance | + conf.hasFlow(DataFlow::exprNode(this), instance) and + result = getInstanceInput(instance, this.getConstructor().getDeclaringType()) + ) + } +} + +/** Gets an input, for example `input` in `instance.password = input`. */ +private Expr getInstanceInput(DataFlow::Node instance, RefType t) { + exists(AssignExpr a, FieldAccess fa | + instance = DataFlow::getFieldQualifier(fa) and + a.getDest() = fa and + a.getSource() = result and + fa.getField().getDeclaringType() = t + | + t.getASourceSupertype*() instanceof TypeSerializable or + t instanceof JAXBElement + ) +} + +private class ClassStoreFlowConfig extends DataFlow4::Configuration { + ClassStoreFlowConfig() { this = "SensitiveStorage::ClassStoreFlowConfig" } + override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof ClassStore } + override predicate isSink(DataFlow::Node sink) { + exists(getInstanceInput(sink, _)) or + serializableStore(sink, _) or + marshallableStore(sink, _) + } + override int fieldFlowBranchLimit() { result = 1 } +} + +private predicate serializableStore(DataFlow::Node instance, Expr store) { + exists(MethodAccess m | + store = m and + m.getMethod() instanceof WriteObjectMethod and + instance.asExpr() = m.getArgument(0) + ) +} + +private predicate marshallableStore(DataFlow::Node instance, Expr store) { + exists(MethodAccess m | + store = m and + m.getMethod() instanceof JAXBMarshalMethod and + instance.asExpr() = m.getArgument(0) + ) +} + +/** + * The instantiation of a serializable class, which can be stored to disk. + * + * Only includes tainted instances where data from a `SensitiveSource` may flow + * to an input of the `Serializable`. + */ +class Serializable extends ClassStore { + Serializable() { + this.getConstructor().getDeclaringType().getASupertype*() instanceof TypeSerializable and + // `Properties` are `Serializable`, but handled elsewhere. + not this instanceof Properties and + // restrict attention to tainted instances + exists(SensitiveSource data | data.flowsToCached(getInstanceInput(_, this.getConstructor().getDeclaringType()))) + } + + /** Gets a store, for example `outputStream.writeObject(instance)`. */ + override Expr getAStore() { + exists(ClassStoreFlowConfig conf, DataFlow::Node n | + serializableStore(n, result) and + conf.hasFlow(DataFlow::exprNode(this), n) + ) + } +} + +/** The instantiation of a marshallable class, which can be stored to disk as XML. */ +class Marshallable extends ClassStore { + Marshallable() { + this.getConstructor().getDeclaringType() instanceof JAXBElement + } + + /** Gets a store, for example `marshaller.marshal(instance)`. */ + override Expr getAStore() { + exists(ClassStoreFlowConfig conf, DataFlow::Node n | + marshallableStore(n, result) and + conf.hasFlow(DataFlow::exprNode(this), n) + ) + } +} diff --git a/java/ql/src/Security/CWE/CWE-319/HttpsUrls.java b/java/ql/src/Security/CWE/CWE-319/HttpsUrls.java new file mode 100644 index 00000000000..070e064e00b --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-319/HttpsUrls.java @@ -0,0 +1,35 @@ +public static void main(String[] args) { + { + try { + String protocol = "http://"; + URL u = new URL(protocol + "www.secret.example.org/"); + // BAD: This causes a 'ClassCastException' at runtime, because the + // HTTP URL cannot be used to make an 'HttpsURLConnection', + // which enforces SSL. + HttpsURLConnection hu = (HttpsURLConnection) u.openConnection(); + hu.setRequestMethod("PUT"); + hu.connect(); + OutputStream os = hu.getOutputStream(); + hu.disconnect(); + } + catch (IOException e) { + // fail + } + } + + { + try { + String protocol = "https://"; + URL u = new URL(protocol + "www.secret.example.org/"); + // GOOD: Opening a connection to a URL using HTTPS enforces SSL. + HttpsURLConnection hu = (HttpsURLConnection) u.openConnection(); + hu.setRequestMethod("PUT"); + hu.connect(); + OutputStream os = hu.getOutputStream(); + hu.disconnect(); + } + catch (IOException e) { + // fail + } + } +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-319/HttpsUrls.qhelp b/java/ql/src/Security/CWE/CWE-319/HttpsUrls.qhelp new file mode 100644 index 00000000000..8b8b0eff847 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-319/HttpsUrls.qhelp @@ -0,0 +1,49 @@ + + + +

    Constructing URLs with the HTTP protocol can lead to unsecured connections.

    + +

    Furthermore, constructing URLs with the HTTP protocol can create problems if other parts of the +code expect HTTPS URLs. A typical pattern is to cast the URLConnection that is produced +by url.getConnection() to an HttpsURLConnection. This is impossible if the +URL that has been constructed uses HTTP rather than HTTPS, and results in a run-time ClassCastException.

    + +
    + + +

    When you construct a URL using java.net.URL, ensure that you use an HTTPS +URL rather than an HTTP URL. Then, any connections that are made using that URL are secure +SSL connections.

    + +
    + + +

    The following example shows two ways of opening a connection using a URL. When the connection is +opened using an HTTP URL rather than an HTTPS URL, the connection is unsecured, and in this case a +ClassCastException is caused. When the connection is opened using an HTTPS URL, the +connection is a secure SSL connection.

    + + + +
    + + +
  • The CERT Oracle Secure Coding Standard for Java: + SER03-J. Do not serialize unencrypted, sensitive data.
  • +
  • Java Platform, Standard Edition 7, API Specification: + +Class HttpsURLConnection.
  • +
  • +OWASP: +Transport Layer Protection Cheat Sheet. +
  • + + + + + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-319/HttpsUrls.ql b/java/ql/src/Security/CWE/CWE-319/HttpsUrls.ql new file mode 100644 index 00000000000..864bf9cb061 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-319/HttpsUrls.ql @@ -0,0 +1,70 @@ +/** + * @name Failure to use HTTPS URLs + * @description Non-HTTPS connections can be intercepted by third parties. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/non-https-url + * @tags security + * external/cwe/cwe-319 + */ +import java +import semmle.code.java.dataflow.TaintTracking + +class HTTPString extends StringLiteral { + HTTPString() { + // Avoid matching "https" here. + exists(string s | this.getRepresentedString() = s | + ( + // Either the literal "http", ... + s = "http" or + // ... or the beginning of a http URL. + s.matches("http://%") + ) and + not s.matches("%/localhost%") + ) + } +} + +class URLConstructor extends ClassInstanceExpr { + URLConstructor() { + this.getConstructor().getDeclaringType().getQualifiedName() = "java.net.URL" + } + + Expr protocolArg() { + // In all cases except where the first parameter is a URL, the argument + // containing the protocol is the first one, otherwise it is the second. + if this.getConstructor().getParameter(0).getType().getName() = "URL" + then result = this.getArgument(1) + else result = this.getArgument(0) + } +} + +class URLOpenMethod extends Method { + URLOpenMethod() { + this.getDeclaringType().getQualifiedName() = "java.net.URL" and + ( + this.getName() = "openConnection" or + this.getName() = "openStream" + ) + } +} + +class HTTPStringToURLOpenMethodFlowConfig extends TaintTracking::Configuration { + HTTPStringToURLOpenMethodFlowConfig() { this = "HttpsUrls::HTTPStringToURLOpenMethodFlowConfig" } + override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof HTTPString } + override predicate isSink(DataFlow::Node sink) { exists(MethodAccess m | sink.asExpr() = m.getQualifier() and m.getMethod() instanceof URLOpenMethod) } + override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { + exists(URLConstructor u | + node1.asExpr() = u.protocolArg() and + node2.asExpr() = u + ) + } + override predicate isSanitizer(DataFlow::Node node) { node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType } +} + +from MethodAccess m, HTTPString s +where + any(HTTPStringToURLOpenMethodFlowConfig c).hasFlow(DataFlow::exprNode(s), DataFlow::exprNode(m.getQualifier())) +select m, "URL may have been constructed with HTTP protocol, using $@.", + s, "this source" diff --git a/java/ql/src/Security/CWE/CWE-319/UseSSL.java b/java/ql/src/Security/CWE/CWE-319/UseSSL.java new file mode 100644 index 00000000000..c2e2a9b938f --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-319/UseSSL.java @@ -0,0 +1,31 @@ +public static void main(String[] args) { + { + try { + URL u = new URL("http://www.secret.example.org/"); + HttpURLConnection httpcon = (HttpURLConnection) u.openConnection(); + httpcon.setRequestMethod("PUT"); + httpcon.connect(); + // BAD: output stream from non-HTTPS connection + OutputStream os = httpcon.getOutputStream(); + httpcon.disconnect(); + } + catch (IOException e) { + // fail + } + } + + { + try { + URL u = new URL("https://www.secret.example.org/"); + HttpsURLConnection httpscon = (HttpsURLConnection) u.openConnection(); + httpscon.setRequestMethod("PUT"); + httpscon.connect(); + // GOOD: output stream from HTTPS connection + OutputStream os = httpscon.getOutputStream(); + httpscon.disconnect(); + } + catch (IOException e) { + // fail + } + } +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-319/UseSSL.qhelp b/java/ql/src/Security/CWE/CWE-319/UseSSL.qhelp new file mode 100644 index 00000000000..5856086330f --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-319/UseSSL.qhelp @@ -0,0 +1,50 @@ + + + +

    Using a stream that is derived from a non-SSL connection or socket can result in an +unsecured connection that is vulnerable to interception.

    + +
    + + +

    Use javax.net.ssl.HttpsURLConnection and javax.net.ssl.SSLSocket instead +of the corresponding unsecured versions in java.net. If necessary, downcast from an HttpURLConnection to +an HttpsURLConnection to enforce the use of SSL. +In addition, when you construct a java.net.URL, ensure that you use the HTTPS protocol, +to avoid exceptions when trying to make HTTPS connections to the URL.

    + +
    + + +

    The following example shows two ways of opening an output stream. When the stream is opened using +httpcon, which is an HttpURLConnection, the connection does not use SSL, +and therefore is vulnerable to attack. When the stream is opened using httpscon, +the connection is a secured SSL connection.

    + + + +
    + + +
  • The CERT Oracle Secure Coding Standard for Java: + SER03-J. Do not serialize unencrypted, sensitive data.
  • +
  • Java Platform, Standard Edition 7, API Specification: + +Class HttpsURLConnection.
  • +
  • Java Platform, Standard Edition 7, API Specification: + +Class SSLSocket.
  • +
  • +OWASP: +Transport Layer Protection Cheat Sheet. +
  • + + + + + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-319/UseSSL.ql b/java/ql/src/Security/CWE/CWE-319/UseSSL.ql new file mode 100644 index 00000000000..fa952d5b76d --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-319/UseSSL.ql @@ -0,0 +1,39 @@ +/** + * @name Failure to use SSL + * @description Non-SSL connections can be intercepted by third parties. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/non-ssl-connection + * @tags security + * external/cwe/cwe-319 + */ +import java +import semmle.code.java.security.Encryption + +class URLConnection extends RefType { + URLConnection() { + this.getAnAncestor().hasQualifiedName("java.net", "URLConnection") and + not this.hasName("JarURLConnection") + } +} + +class Socket extends RefType { + Socket() { + this.getAnAncestor().hasQualifiedName("java.net", "Socket") + } +} + +from MethodAccess m, Class c, string type +where + m.getQualifier().getType() = c and + ( + (c instanceof URLConnection and type = "connection") or + (c instanceof Socket and type = "socket") + ) and + not c instanceof SSLClass and + ( + m.getMethod().getName() = "getInputStream" or + m.getMethod().getName() = "getOutputStream" + ) +select m, "Stream using vulnerable non-SSL " + type + "." diff --git a/java/ql/src/Security/CWE/CWE-319/UseSSLSocketFactories.java b/java/ql/src/Security/CWE/CWE-319/UseSSLSocketFactories.java new file mode 100644 index 00000000000..ece078110f2 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-319/UseSSLSocketFactories.java @@ -0,0 +1,25 @@ +public static void main(String[] args) { + { + try { + TestImpl obj = new TestImpl(); + + // BAD: default socket factory is used + Test stub = (Test) UnicastRemoteObject.exportObject(obj, 0); + } catch (Exception e) { + // fail + } + } + + { + try { + TestImpl obj = new TestImpl(); + SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory(); + SslRMIServerSocketFactory ssf = new SslRMIServerSocketFactory(); + + // GOOD: SSL factories are used + Test stub = (Test) UnicastRemoteObject.exportObject(obj, 0, csf, ssf); + } catch (Exception e) { + // fail + } + } +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-319/UseSSLSocketFactories.qhelp b/java/ql/src/Security/CWE/CWE-319/UseSSLSocketFactories.qhelp new file mode 100644 index 00000000000..56e9be97345 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-319/UseSSLSocketFactories.qhelp @@ -0,0 +1,44 @@ + + + +

    Various networking and Remote Method Invocation (RMI) methods may use SocketFactories +to specify the kind of connections to use. Using the default factory or a non-SSL factory leads +to vulnerable connections.

    + +
    + + +

    Use SSL factories instead of the default factories. SSL factories can be found in +javax.net.ssl or java.rmi.ssl. If you want to define your own custom +factories, consider inheriting from one of the SSL factories.

    + +
    + + +

    The following example shows two ways of using RMI to export an object. The first use of +exportObject uses the default socket factories. The second use of exportObject +uses explicit SSL factories, which are preferable.

    + + + +
    + + +
  • The CERT Oracle Secure Coding Standard for Java: + SER03-J. Do not serialize unencrypted, sensitive data.
  • +
  • Java Platform, Standard Edition 7, API Specification: + +Class SSLSocketFactory.
  • +
  • +OWASP: +Transport Layer Protection Cheat Sheet. +
  • + + + + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-319/UseSSLSocketFactories.ql b/java/ql/src/Security/CWE/CWE-319/UseSSLSocketFactories.ql new file mode 100644 index 00000000000..7c304dc53e0 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-319/UseSSLSocketFactories.ql @@ -0,0 +1,81 @@ +/** + * @name Failure to use SSL socket factories + * @description Connections that are specified by non-SSL socket factories can be intercepted by + * third parties. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/non-ssl-socket-factory + * @tags security + * external/cwe/cwe-319 + */ +import java +import semmle.code.java.security.Encryption + +class NetworkClass extends Class { + NetworkClass() { + this.getAnAncestor().getQualifiedName().matches("java.rmi.%") or + this.getAnAncestor().getQualifiedName().matches("java.net.%") or + this.getAnAncestor().getQualifiedName().matches("javax.net.%") + } +} + +class SocketFactoryType extends RefType { + SocketFactoryType() { + this.getQualifiedName() = "java.rmi.server.RMIServerSocketFactory" or + this.getQualifiedName() = "java.rmi.server.RMIClientSocketFactory" or + this.getQualifiedName()= "javax.net.SocketFactory" or + this.getQualifiedName() = "java.net.SocketImplFactory" + } +} + + +/** Holds if the method `m` has a factory parameter at location `p`. */ +cached +predicate usesFactory(Method m, int p) { + m.getParameter(p).getType().(RefType).getAnAncestor() instanceof SocketFactoryType +} + +/** Holds if the method `m` has an overloaded method with a factory parameter. */ +predicate overloadUsesFactories(Method m, Method overload) { + overload.getAParamType().(RefType).getAnAncestor() instanceof SocketFactoryType and + overloads(m, overload) +} + +predicate overloads(Method m1, Method m2) { + m1 != m2 and + exists(RefType t, string name | + methodInfo(m1, t, name) and + methodInfo(m2, t, name) + ) +} + +predicate methodInfo(Method m, RefType t, string name) { + m.getDeclaringType() = t and + m.getName() = name +} + +predicate query(MethodAccess m, Method def, int paramNo, string message, Element evidence) { + m.getMethod() = def and + // Using a networking method. + def.getDeclaringType() instanceof NetworkClass and + ( + // Either the method has a factory parameter that is used, but not with + // an SSL factory, ... + usesFactory(def, paramNo) and + evidence = m.getArgument(paramNo) and + not evidence.(Expr).getType() instanceof SSLClass and + message = "has a non-SSL factory argument " + or + // ... or there is an overloaded method on the same type that does take a factory, + // which could be used for SSL. + overloadUsesFactories(def, evidence) and + paramNo = 0 and + message = "could use custom factories via overloaded method " + ) +} + +from MethodAccess m, Method def, int param, string message, Element evidence +where + query(m,def,param,message,evidence) +select m, "Method " + message + ": use an SSL factory." diff --git a/java/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.java b/java/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.java new file mode 100644 index 00000000000..7d8299f2369 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.java @@ -0,0 +1,12 @@ +// BAD: DES is a weak algorithm +Cipher des = Cipher.getInstance("DES"); +cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); + +byte[] encrypted = cipher.doFinal(input.getBytes("UTF-8")); + +// ... + +// GOOD: AES is a strong algorithm +Cipher des = Cipher.getInstance("AES"); + +// ... \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.qhelp b/java/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.qhelp new file mode 100644 index 00000000000..cd3f86f6bed --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.qhelp @@ -0,0 +1,41 @@ + + + +

    Using broken or weak cryptographic algorithms can leave data vulnerable to being decrypted.

    + +

    Many cryptographic algorithms provided by cryptography libraries are known to be weak, or +flawed. Using such an algorithm means that an attacker may be able to easily decrypt the encrypted +data.

    + +
    + + +

    Ensure that you use a strong, modern cryptographic algorithm. Use at least AES-128 or RSA-2048.

    + +
    + + +

    The following code shows an example of using a java Cipher to encrypt some data. +When creating a Cipher instance, you must specify the encryption algorithm to use. The first +example uses DES, which is an older algorithm that is now considered weak. The second example uses AES, which +is a strong modern algorithm.

    + + + +
    + + +
  • NIST, FIPS 140 Annex a: +Approved Security Functions.
  • +
  • NIST, SP 800-131A: +Transitions: Recommendation for Transitioning the Use of Cryptographic Algorithms and Key Lengths.
  • + + + + + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.ql b/java/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.ql new file mode 100644 index 00000000000..3106a21af13 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.ql @@ -0,0 +1,46 @@ +/** + * @name Use of a broken or risky cryptographic algorithm + * @description Using broken or weak cryptographic algorithms can allow an attacker to compromise security. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/weak-cryptographic-algorithm + * @tags security + * external/cwe/cwe-327 + */ +import java +import semmle.code.java.security.Encryption +import semmle.code.java.dataflow.TaintTracking +import DataFlow + +private class ShortStringLiteral extends StringLiteral { + ShortStringLiteral() { + getLiteral().length() < 100 + } +} + +class BrokenAlgoLiteral extends ShortStringLiteral { + BrokenAlgoLiteral() { + getValue().regexpMatch(algorithmBlacklistRegex()) and + // Exclude German and French sentences. + not getValue().regexpMatch(".*\\p{IsLowercase} des \\p{IsLetter}.*") + } +} + +class InsecureCryptoConfiguration extends TaintTracking::Configuration { + InsecureCryptoConfiguration() { this = "BrokenCryptoAlgortihm::InsecureCryptoConfiguration" } + override predicate isSource(Node n) { + n.asExpr() instanceof BrokenAlgoLiteral + } + override predicate isSink(Node n) { + exists(CryptoAlgoSpec c | n.asExpr() = c.getAlgoSpec()) + } + override predicate isSanitizer(DataFlow::Node node) { node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType } +} + +from CryptoAlgoSpec c, Expr a, BrokenAlgoLiteral s, InsecureCryptoConfiguration conf +where + a = c.getAlgoSpec() and + conf.hasFlow(exprNode(s), exprNode(a)) +select c, "Cryptographic algorithm $@ is weak and should not be used.", + s, s.getLiteral() diff --git a/java/ql/src/Security/CWE/CWE-327/MaybeBrokenCryptoAlgorithm.qhelp b/java/ql/src/Security/CWE/CWE-327/MaybeBrokenCryptoAlgorithm.qhelp new file mode 100644 index 00000000000..bf98e2495df --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-327/MaybeBrokenCryptoAlgorithm.qhelp @@ -0,0 +1,5 @@ + + + diff --git a/java/ql/src/Security/CWE/CWE-327/MaybeBrokenCryptoAlgorithm.ql b/java/ql/src/Security/CWE/CWE-327/MaybeBrokenCryptoAlgorithm.ql new file mode 100644 index 00000000000..2de43017893 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-327/MaybeBrokenCryptoAlgorithm.ql @@ -0,0 +1,73 @@ +/** + * @name Use of a potentially broken or risky cryptographic algorithm + * @description Using broken or weak cryptographic algorithms can allow an attacker to compromise security. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/potentially-weak-cryptographic-algorithm + * @tags security + * external/cwe/cwe-327 + */ +import java +import semmle.code.java.security.Encryption +import semmle.code.java.dataflow.TaintTracking +import DataFlow +import semmle.code.java.dispatch.VirtualDispatch + +private class ShortStringLiteral extends StringLiteral { + ShortStringLiteral() { + getLiteral().length() < 100 + } +} + +class InsecureAlgoLiteral extends ShortStringLiteral { + InsecureAlgoLiteral() { + // Algorithm identifiers should be at least two characters. + getValue().length() > 1 and + exists(string s | s = getLiteral() | + not s.regexpMatch(algorithmWhitelistRegex()) and + // Exclude results covered by another query. + not s.regexpMatch(algorithmBlacklistRegex()) + ) + } +} + +predicate objectToString(MethodAccess ma) { + exists(Method m | + m = ma.getMethod() and + m.hasName("toString") and + m.getDeclaringType() instanceof TypeObject and + variableTrack(ma.getQualifier()).getType().getErasure() instanceof TypeObject + ) +} + +class StringContainer extends RefType { + StringContainer() { + this instanceof TypeString or + this.hasQualifiedName("java.lang", "StringBuilder") or + this.hasQualifiedName("java.lang", "StringBuffer") or + this.hasQualifiedName("java.util", "StringTokenizer") or + this.(Array).getComponentType() instanceof StringContainer + } +} + +class InsecureCryptoConfiguration extends TaintTracking::Configuration { + InsecureCryptoConfiguration() { this = "InsecureCryptoConfiguration" } + override predicate isSource(Node n) { + n.asExpr() instanceof InsecureAlgoLiteral + } + override predicate isSink(Node n) { + exists(CryptoAlgoSpec c | n.asExpr() = c.getAlgoSpec()) + } + override predicate isSanitizer(Node n) { + objectToString(n.asExpr()) or + not n.getType().getErasure() instanceof StringContainer + } +} + +from CryptoAlgoSpec c, Expr a, InsecureAlgoLiteral s, InsecureCryptoConfiguration conf +where + a = c.getAlgoSpec() and + conf.hasFlow(exprNode(s), exprNode(a)) +select c, "Cryptographic algorithm $@ may not be secure, consider using a different algorithm.", + s, s.getLiteral() diff --git a/java/ql/src/Security/CWE/CWE-335/PredictableSeed.java b/java/ql/src/Security/CWE/CWE-335/PredictableSeed.java new file mode 100644 index 00000000000..bfe367afe97 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-335/PredictableSeed.java @@ -0,0 +1,14 @@ +SecureRandom prng = new SecureRandom(); +int randomData = 0; + +// BAD: Using a constant value as a seed for a random number generator means all numbers it generates are predictable. +prng.setSeed(12345L); +randomData = prng.next(32); + +// BAD: System.currentTimeMillis() returns the system time which is predictable. +prng.setSeed(System.currentTimeMillis()); +randomData = prng.next(32); + +// GOOD: SecureRandom implementations seed themselves securely by default. +prng = new SecureRandom(); +randomData = prng.next(32); diff --git a/java/ql/src/Security/CWE/CWE-335/PredictableSeed.qhelp b/java/ql/src/Security/CWE/CWE-335/PredictableSeed.qhelp new file mode 100644 index 00000000000..2c62fb4517c --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-335/PredictableSeed.qhelp @@ -0,0 +1,36 @@ + + + +

    Using a predictable seed in a pseudo-random number generator can lead to predictability of the numbers +generated by it.

    + +
    + +

    If the predictability of the pseudo-random number generator does not matter then consider using the faster +Random class from java.util. If it is important that the pseudo-random number +generator produces completely unpredictable values then either let the generator securely seed itself by not +specifying a seed or specify a randomly generated, unpredictable seed.

    + +
    + +

    In the first example shown here, a constant value is used as a seed. Depending on the implementation of +SecureRandom, this could lead to the same random number being generated each time the code is executed.

    +

    In the second example shown here, the system time is used as a seed. Depending on the implementation of +SecureRandom, if an attacker knows what time the code was run, they could predict the generated random +number.

    +

    In the third example shown here, the random number generator is allowed to generate its own seed, which it +will do in a secure way.

    + + + +
    + + + + + + +
    diff --git a/java/ql/src/Security/CWE/CWE-335/PredictableSeed.ql b/java/ql/src/Security/CWE/CWE-335/PredictableSeed.ql new file mode 100644 index 00000000000..4cd0ad9c7ee --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-335/PredictableSeed.ql @@ -0,0 +1,18 @@ +/** + * @name Use of a predictable seed in a secure random number generator + * @description Using a predictable seed in a pseudo-random number generator can lead to predictability of the numbers generated by it. + * @kind problem + * @problem.severity error + * @precision high + * @id java/predictable-seed + * @tags security + */ +import java +import semmle.code.java.security.Random + +from GetRandomData da, RValue use, PredictableSeedExpr source +where + da.getQualifier() = use and + unsafelySeeded(use, source) +select da, "Usage of a SecureRandom number generator seeded with a $@.", + source, "predictable value" diff --git a/java/ql/src/Security/CWE/CWE-367/TOCTOURace.java b/java/ql/src/Security/CWE/CWE-367/TOCTOURace.java new file mode 100644 index 00000000000..85a49ec9b04 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-367/TOCTOURace.java @@ -0,0 +1,27 @@ +class Resource { + public synchronized boolean isReady() { ... } + + public synchronized void setReady(boolean ready) { ... } + + public synchronized void act() { + if (!isReady()) + throw new IllegalStateException(); + ... + } +} + +public synchronized void bad(Resource r) { + if (r.isReady()) { + // r might no longer be ready, another thread might + // have called setReady(false) + r.act(); + } +} + +public synchronized void good(Resource r) { + synchronized(r) { + if (r.isReady()) { + r.act(); + } + } +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-367/TOCTOURace.qhelp b/java/ql/src/Security/CWE/CWE-367/TOCTOURace.qhelp new file mode 100644 index 00000000000..68101683b37 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-367/TOCTOURace.qhelp @@ -0,0 +1,53 @@ + + + + +

    +Often it is necessary to check the state of a resource before using it. +If the resource is accessed concurrently, then the check and the use +need to be performed atomically, otherwise the state of the resource may change between the check +and the use. This can lead to a "time-of-check/time-of-use" (TOCTOU) race condition. +

    + +

    +In Java, classes may present state inspection methods and operation methods which are synchronized. +This prevents multiple threads from executing those methods simultaneously, but it does not prevent a +state change in between separate method invocations. +

    +
    + + +

    When calling a series of methods which require a consistent view of an object, make sure to synchronize +on a monitor that will prevent any other access to the object during your operations. +

    +

    +If the class that you are using has a well-designed interface, then synchronizing on the object itself will +prevent its state being changed inappropriately. +

    +
    + + + +

    +The following example shows a resource which has a readiness state, and an action that is only valid if the +resource is ready. +

    + +

    +In the bad case, the caller checks the readiness state and then acts, but does not synchronize around the +two calls, so the readiness state may be changed by another thread. +

    + +

    +In the good case, the caller jointly synchronizes the check and the use on the resource, so no other thread +can modify the state before the use. +

    + + + +
    + + +
    diff --git a/java/ql/src/Security/CWE/CWE-367/TOCTOURace.ql b/java/ql/src/Security/CWE/CWE-367/TOCTOURace.ql new file mode 100644 index 00000000000..a70a2e4deb2 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-367/TOCTOURace.ql @@ -0,0 +1,132 @@ +/** + * @name Time-of-check time-of-use race condition + * @description Using a resource after an unsynchronized state check can lead to a race condition, + * if the state may be changed between the check and use. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/toctou-race-condition + * @tags security + * external/cwe/cwe-367 + */ +import java +import semmle.code.java.Concurrency +import semmle.code.java.controlflow.Guards + +/** + * Holds if `e1` and `e2` appear within a `synchronized` block on `monitor`. + */ +predicate commonSynchronization(Expr e1, Expr e2, Variable monitor) { + exists(SynchronizedStmt s | + locallySynchronizedOn(e1, s, monitor) and + locallySynchronizedOn(e2, s, monitor) + ) +} + +/** + * Holds if `m` is a call to a synchronized method on `receiver`. + */ +predicate synchCallOn(MethodAccess m, Variable receiver) { + m.getCallee() instanceof SynchronizedCallable and + m.getQualifier() = receiver.getAnAccess() +} + +/** + * A callable that might be used concurrently. This is a heuristic to avoid flagging + * non-concurrent usage of classes that try to be concurrency-safe (e.g. a lot of the Java + * collections). + */ +class PossiblyConcurrentCallable extends Callable { + PossiblyConcurrentCallable() { + this instanceof SynchronizedCallable + or + exists(SynchronizedStmt s | s.getEnclosingCallable() = this) + or + exists(FieldAccess f | f.getVariable().isVolatile() | f.getEnclosingCallable() = this) + or + exists(VarAccess v | v.getVariable().getType().(RefType).hasQualifiedName("java.lang", "ThreadLocal") | + v.getEnclosingCallable() = this + ) + } +} + +/** + * Holds if all accesses to `v` (outside of initializers) are locked in the same way. + */ +predicate alwaysLocked(Field f) { + exists(Variable lock | + forex(VarAccess access | + access = f.getAnAccess() and not access.getEnclosingCallable() instanceof InitializerMethod + | + locallySynchronizedOn(access, _, lock) + ) + ) + or + exists(RefType thisType | + forex(VarAccess access | + access = f.getAnAccess() and not access.getEnclosingCallable() instanceof InitializerMethod + | + locallySynchronizedOnThis(access, thisType) + ) + ) + or + exists(RefType classType | + forex(VarAccess access | + access = f.getAnAccess() and not access.getEnclosingCallable() instanceof InitializerMethod + | + locallySynchronizedOnClass(access, classType) + ) + ) +} + +/** + * Holds if the value of `v` probably never escapes the local scope. + */ +predicate probablyNeverEscapes(LocalVariableDecl v) { + // Not passed into another function. + not exists(Call c | c.getAnArgument() = v.getAnAccess()) + and + // Not assigned directly to another variable. + not exists(Assignment a | a.getSource() = v.getAnAccess()) + and + // Not returned. + not exists(ReturnStmt r | r.getResult() = v.getAnAccess()) + and + // All assignments are to new instances of a class. + forex(Expr e | e = v.getAnAssignedValue() | e instanceof ClassInstanceExpr) +} + +// Loop conditions tend to be uninteresting, so are not included. +from IfStmt check, MethodAccess call1, MethodAccess call2, Variable r +where + check.getCondition().getAChildExpr*() = call1 + and + // This can happen if there are loops, etc. + not call1 = call2 + and + // The use is controlled by one of the branches of the condition, i.e. whether it + // is reached actually depends on that condition. + call1.getBasicBlock().(ConditionBlock).controls(call2.getBasicBlock(), _) + and + // Two calls to synchronized methods on the same variable. + synchCallOn(call1, r) and synchCallOn(call2, r) + and + // Not jointly synchronized on that variable. + // (If the caller synchronizes on `r` then it takes the same monitor as the `synchronized` callees do.) + not commonSynchronization(call1, call2, r) + and + // Only include cases that look like they may be intended for concurrent usage. + check.getEnclosingCallable() instanceof PossiblyConcurrentCallable + and + // Ignore fields that look like they're consistently guarded with some other lock. + not alwaysLocked(r) + and + // Ignore local variables whose value probably never escapes, as they can't be accessed concurrently. + not probablyNeverEscapes(r) + and + // The synchronized methods on `Throwable` are not interesting. + not call1.getCallee().getDeclaringType() instanceof TypeThrowable +select call2, + "The state of $@ is checked $@, and then it is used here. But these are not jointly synchronized.", + r, r.getName(), + call1, "here" diff --git a/java/ql/src/Security/CWE/CWE-421/SocketAuthRace.java b/java/ql/src/Security/CWE/CWE-421/SocketAuthRace.java new file mode 100644 index 00000000000..d85e43f0d85 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-421/SocketAuthRace.java @@ -0,0 +1,19 @@ +public void doConnect(int desiredPort, String username) { + ServerSocket listenSocket = new ServerSocket(desiredPort); + + if (isAuthenticated(username)) { + Socket connection1 = listenSocket.accept(); + // BAD: no authentication over the socket connection + connection1.getOutputStream().write(secretData); + } +} + +public void doConnect(int desiredPort, String username) { + ServerSocket listenSocket = new ServerSocket(desiredPort); + + Socket connection2 = listenSocket.accept(); + // GOOD: authentication happens over the socket + if (doAuthenticate(connection2, username)) { + connection2.getOutputStream().write(secretData); + } +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-421/SocketAuthRace.qhelp b/java/ql/src/Security/CWE/CWE-421/SocketAuthRace.qhelp new file mode 100644 index 00000000000..173c96bb285 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-421/SocketAuthRace.qhelp @@ -0,0 +1,43 @@ + + + +

    +A common pattern is to have a channel of communication open with a user, and then to open +another channel, for example to transfer data. However, if user authentication is done over +the original channel rather than the alternate +channel, then an attacker may be able to connect to the alternate channel before the legitimate +user does. This allows the attacker to impersonate the user by "piggybacking" on +any previous authentication. +

    +
    + + +

    +When opening an alternate channel for an authenticated user (for example, a Java Socket), +always authenticate the user over the new channel. +

    + +
    + + +

    +This example shows two ways of opening a connection for a user. In the first example, authentication is +determined based on materials that the user has already provided (for example, their username and/or password), and then +a new channel is opened. However, no authentication is done over the new channel, and so an attacker +could connect to it before the user connects. +

    + +

    +In the second example, authentication is done over the socket channel itself, which verifies that the newly connected +user is in fact the user that was expected. +

    + + + +
    + + + +
    diff --git a/java/ql/src/Security/CWE/CWE-421/SocketAuthRace.ql b/java/ql/src/Security/CWE/CWE-421/SocketAuthRace.ql new file mode 100644 index 00000000000..71c5f9adc4f --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-421/SocketAuthRace.ql @@ -0,0 +1,83 @@ +/** + * @name Race condition in socket authentication + * @description Opening a socket after authenticating via a different channel may allow an attacker to connect to the port first. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/socket-auth-race-condition + * @tags security + * external/cwe/cwe-421 + */ + +import java +import semmle.code.java.security.SensitiveActions +import semmle.code.java.controlflow.Dominance +import semmle.code.java.controlflow.Guards + +abstract class ConnectionMethod extends Method {} + +/* + * Amongst the Java networking utilities, the `ServerSocket*` classes allow listening on a port. + * By contrast, the `Socket*` classes require connection to a specific address. + * + * Listening (server) sockets are much more vulnerable to an unexpected user taking the connection, + * as the connection is initiated by the user. It would be possible to implement this attack on client + * socket connections, but this would require MITM-ing the connection between the authentication of the + * server and the opening of the alternate channel, which seems quite tricky. + */ + +/** + * The `accept` method on `ServerSocket`, which listens for a connection and returns when one has + * been established. + */ +class ServerSocketAcceptMethod extends ConnectionMethod { + ServerSocketAcceptMethod() { + this.getName() = "accept" + and + this.getDeclaringType().hasQualifiedName("java.net", "ServerSocket") + } +} + +/** + * The `accept` method on `ServerSocketChannel`, which listens for a connection and returns when one has + * been established. + */ +class ServerSocketChannelAcceptMethod extends ConnectionMethod { + ServerSocketChannelAcceptMethod() { + this.getName() = "accept" + and + this.getDeclaringType().hasQualifiedName("java.nio.channels", "ServerSocketChannel") + } +} + +predicate controlledByAuth(Expr controlled, Expr condition) { + exists(ConditionBlock b | + condition = b.getCondition() + and + b.controls(controlled.getBasicBlock(), _) + and + condition.(MethodAccess).getMethod() instanceof AuthMethod + ) +} + +/* + * The approach here is to look for connections where the opening + * of the connection is guarded by an authentication check. + * While this is not exactly what we want to look for (we want to + * look for the absence of authentication over the new channel): + * - If you are authenticating over the new channel, then there is + * little point in authenticating again beforehand. So we can + * assume that these cases are likely to be disjoint. + * - Spotting something that looks like an authentication check is + * a heuristic that tells us that this connection is meant to be + * authenticated. If we checked every connection, we would have + * no idea which ones were meant to be secure. + */ +from MethodAccess connection, Expr condition +where + connection.getMethod() instanceof ConnectionMethod + and + controlledByAuth(connection, condition) +select connection, + "This connection occurs after the authentication in $@, rather than authentication over the new connection.", + condition, "this condition" diff --git a/java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.qhelp b/java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.qhelp new file mode 100644 index 00000000000..4fb0ad88b45 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.qhelp @@ -0,0 +1,80 @@ + + + + +

    +Deserializing untrusted data using any deserialization framework that +allows the construction of arbitrary serializable objects is easily exploitable +and in many cases allows an attacker to execute arbitrary code. Even before a +deserialized object is returned to the caller of a deserialization method a lot +of code may have been executed, including static initializers, constructors, +and finalizers. Automatic deserialization of fields means that an attacker may +craft a nested combination of objects on which the executed initialization code +may have unforeseen effects, such as the execution of arbitrary code. +

    +

    +There are many different serialization frameworks. This query currently +supports Kryo, XmlDecoder, XStream, SnakeYaml, and Java IO serialization through +ObjectInputStream/ObjectOutputStream. +

    +
    + + +

    +Avoid deserialization of untrusted data if at all possible. If the +architecture permits it then use other formats instead of serialized objects, +for example JSON or XML. However, these formats should not be deserialized +into complex objects because this provides further opportunities for attack. +For example, XML-based deserialization attacks +are possible through libraries such as XStream and XmlDecoder. + +Alternatively, a tightly controlled whitelist can limit the vulnerability of code, but be aware +of the existence of so-called Bypass Gadgets, which can circumvent such +protection measures. +

    +
    + + +

    +The following example calls readObject directly on an +ObjectInputStream that is constructed from untrusted data, and is +therefore inherently unsafe. +

    + + +

    +Rewriting the communication protocol to only rely on reading primitive types +from the input stream removes the vulnerability. +

    + + +
    + + + +
  • +OWASP vulnerability description: +Deserialization of untrusted data. +
  • +
  • +OWASP guidance on deserializing objects: +Deserialization Cheat Sheet. +
  • +
  • +Talks by Chris Frohoff & Gabriel Lawrence: + +AppSecCali 2015: Marshalling Pickles - how deserializing objects will ruin your day, +OWASP SD: Deserialize My Shorts: +Or How I Learned to Start Worrying and Hate Java Object Deserialization. +
  • +
  • +Alvaro Muñoz & Christian Schneider, RSAConference 2016: +Serial Killer: Silently Pwning Your Java Endpoints. +
  • +
  • +SnakeYaml documentation on deserialization: +SnakeYaml deserialization. +
  • +
    + +
    diff --git a/java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.ql b/java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.ql new file mode 100644 index 00000000000..13e1e42d803 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.ql @@ -0,0 +1,24 @@ +/** + * @name Deserialization of user-controlled data + * @description Deserializing user-controlled data may allow attackers to + * execute arbitrary code. + * @kind problem + * @problem.severity error + * @precision high + * @id java/unsafe-deserialization + * @tags security + * external/cwe/cwe-502 + */ +import java +import semmle.code.java.dataflow.FlowSources +import UnsafeDeserialization + +class UnsafeDeserializationConfig extends TaintTracking::Configuration { + UnsafeDeserializationConfig() { this = "UnsafeDeserializationConfig" } + override predicate isSource(DataFlow::Node source) { source instanceof RemoteUserInput } + override predicate isSink(DataFlow::Node sink) { sink instanceof UnsafeDeserializationSink } +} + +from UnsafeDeserializationSink sink, RemoteUserInput source, UnsafeDeserializationConfig conf +where conf.hasFlow(source, sink) +select sink.getMethodAccess(), "Unsafe deserialization of $@.", source, "user input" diff --git a/java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.qll b/java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.qll new file mode 100644 index 00000000000..32f6815dbfa --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-502/UnsafeDeserialization.qll @@ -0,0 +1,71 @@ +import semmle.code.java.frameworks.Kryo +import semmle.code.java.frameworks.XStream +import semmle.code.java.frameworks.SnakeYaml + +class ObjectInputStreamReadObjectMethod extends Method { + ObjectInputStreamReadObjectMethod() { + this.getDeclaringType().getASourceSupertype*().hasQualifiedName("java.io", "ObjectInputStream") and + (this.hasName("readObject") or this.hasName("readUnshared")) + } +} + +class XMLDecoderReadObjectMethod extends Method { + XMLDecoderReadObjectMethod() { + this.getDeclaringType().hasQualifiedName("java.beans", "XMLDecoder") and + this.hasName("readObject") + } +} + +class SafeXStream extends DataFlow2::Configuration { + SafeXStream() { this = "UnsafeDeserialization::SafeXStream" } + override predicate isSource(DataFlow::Node src) { + any(XStreamEnableWhiteListing ma).getQualifier().(VarAccess).getVariable().getAnAccess() = src.asExpr() + } + override predicate isSink(DataFlow::Node sink) { + exists(MethodAccess ma | + sink.asExpr() = ma.getQualifier() and + ma.getMethod() instanceof XStreamReadObjectMethod + ) + } +} + +class SafeKryo extends DataFlow2::Configuration { + SafeKryo() { this = "UnsafeDeserialization::SafeKryo" } + override predicate isSource(DataFlow::Node src) { + any(KryoEnableWhiteListing ma).getQualifier().(VarAccess).getVariable().getAnAccess() = src.asExpr() + } + override predicate isSink(DataFlow::Node sink) { + exists(MethodAccess ma | + sink.asExpr() = ma.getQualifier() and + ma.getMethod() instanceof KryoReadObjectMethod + ) + } +} + +predicate unsafeDeserialization(MethodAccess ma, Expr sink) { + exists(Method m | m = ma.getMethod() | + m instanceof ObjectInputStreamReadObjectMethod and + sink = ma.getQualifier() + or + m instanceof XMLDecoderReadObjectMethod and + sink = ma.getQualifier() + or + m instanceof XStreamReadObjectMethod and + sink = ma.getAnArgument() and + not exists(SafeXStream sxs | sxs.hasFlowToExpr(ma.getQualifier())) + or + m instanceof KryoReadObjectMethod and + sink = ma.getAnArgument() and + not exists(SafeKryo sk | sk.hasFlowToExpr(ma.getQualifier())) + or + ma instanceof UnsafeSnakeYamlParse and + sink = ma.getArgument(0) + ) +} + +class UnsafeDeserializationSink extends DataFlow::ExprNode { + UnsafeDeserializationSink() { + unsafeDeserialization(_, this.getExpr()) + } + MethodAccess getMethodAccess() { unsafeDeserialization(result, this.getExpr()) } +} diff --git a/java/ql/src/Security/CWE/CWE-502/UnsafeDeserializationBad.java b/java/ql/src/Security/CWE/CWE-502/UnsafeDeserializationBad.java new file mode 100644 index 00000000000..d5a37b7bf0f --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-502/UnsafeDeserializationBad.java @@ -0,0 +1,12 @@ +public MyObject { + public int field; + MyObject(int field) { + this.field = field; + } +} + +public MyObject deserialize(Socket sock) { + try(ObjectInputStream in = new ObjectInputStream(sock.getInputStream())) { + return (MyObject)in.readObject(); // unsafe + } +} diff --git a/java/ql/src/Security/CWE/CWE-502/UnsafeDeserializationGood.java b/java/ql/src/Security/CWE/CWE-502/UnsafeDeserializationGood.java new file mode 100644 index 00000000000..4a1d5e6c3cd --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-502/UnsafeDeserializationGood.java @@ -0,0 +1,5 @@ +public MyObject deserialize(Socket sock) { + try(DataInputStream in = new DataInputStream(sock.getInputStream())) { + return new MyObject(in.readInt()); + } +} diff --git a/java/ql/src/Security/CWE/CWE-601/UrlRedirect.java b/java/ql/src/Security/CWE/CWE-601/UrlRedirect.java new file mode 100644 index 00000000000..dd915d9eca4 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-601/UrlRedirect.java @@ -0,0 +1,14 @@ +public class UrlRedirect extends HttpServlet { + private static final String VALID_REDIRECT = "http://cwe.mitre.org/data/definitions/601.html"; + + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + // BAD: a request parameter is incorporated without validation into a URL redirect + response.sendRedirect(request.getParameter("target")); + + // GOOD: the request parameter is validated against a known fixed string + if (VALID_REDIRECT.equals(request.getParameter("target"))) { + response.sendRedirect(VALID_REDIRECT); + } + } +} diff --git a/java/ql/src/Security/CWE/CWE-601/UrlRedirect.qhelp b/java/ql/src/Security/CWE/CWE-601/UrlRedirect.qhelp new file mode 100644 index 00000000000..1fdd2be75ac --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-601/UrlRedirect.qhelp @@ -0,0 +1,36 @@ + + + + + +

    Directly incorporating user input into a URL redirect request without validating the input +can facilitate phishing attacks. In these attacks, unsuspecting users can be redirected to a +malicious site that looks very similar to the real site they intend to visit, but which is +controlled by the attacker.

    + +
    + + +

    To guard against untrusted URL redirection, it is advisable to avoid putting user input +directly into a redirect URL. Instead, maintain a list of authorized +redirects on the server; then choose from that list based on the user input provided.

    + +
    + + +

    The following example shows an HTTP request parameter being used directly in a URL redirect +without validating the input, which facilitates phishing attacks. +It also shows how to remedy the problem by validating the user input against a known fixed string. +

    + + + +
    + + + + + +
    diff --git a/java/ql/src/Security/CWE/CWE-601/UrlRedirect.ql b/java/ql/src/Security/CWE/CWE-601/UrlRedirect.ql new file mode 100644 index 00000000000..263caebcfc3 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-601/UrlRedirect.ql @@ -0,0 +1,25 @@ +/** + * @name URL redirection from remote source + * @description URL redirection based on unvalidated user-input + * may cause redirection to malicious web sites. + * @kind problem + * @problem.severity error + * @precision high + * @id java/unvalidated-url-redirection + * @tags security + * external/cwe/cwe-601 + */ +import java +import semmle.code.java.dataflow.FlowSources +import UrlRedirect + +class UrlRedirectConfig extends TaintTracking::Configuration { + UrlRedirectConfig() { this = "UrlRedirectConfig" } + override predicate isSource(DataFlow::Node source) { source instanceof RemoteUserInput } + override predicate isSink(DataFlow::Node sink) { sink instanceof UrlRedirectSink } +} + +from UrlRedirectSink sink, RemoteUserInput source, UrlRedirectConfig conf +where conf.hasFlow(source, sink) +select sink, "Potentially untrusted URL redirection due to $@.", + source, "user-provided value" diff --git a/java/ql/src/Security/CWE/CWE-601/UrlRedirect.qll b/java/ql/src/Security/CWE/CWE-601/UrlRedirect.qll new file mode 100644 index 00000000000..d61b5d1dc2a --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-601/UrlRedirect.qll @@ -0,0 +1,23 @@ +import java +import semmle.code.java.frameworks.Servlets +import semmle.code.java.dataflow.DataFlow + +/** + * A URL redirection sink. + */ +class UrlRedirectSink extends DataFlow::ExprNode { + UrlRedirectSink() { + exists(MethodAccess ma | + ma.getMethod() instanceof HttpServletResponseSendRedirectMethod and + this.asExpr() = ma.getArgument(0) + ) + or + exists(MethodAccess ma | + ma.getMethod() instanceof ResponseSetHeaderMethod or + ma.getMethod() instanceof ResponseAddHeaderMethod + | + ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = "Location" and + this.asExpr() = ma.getArgument(1) + ) + } +} diff --git a/java/ql/src/Security/CWE/CWE-601/UrlRedirectLocal.qhelp b/java/ql/src/Security/CWE/CWE-601/UrlRedirectLocal.qhelp new file mode 100644 index 00000000000..05e5cf6fb49 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-601/UrlRedirectLocal.qhelp @@ -0,0 +1,5 @@ + + + diff --git a/java/ql/src/Security/CWE/CWE-601/UrlRedirectLocal.ql b/java/ql/src/Security/CWE/CWE-601/UrlRedirectLocal.ql new file mode 100644 index 00000000000..ad408836ceb --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-601/UrlRedirectLocal.ql @@ -0,0 +1,25 @@ +/** + * @name URL redirection from local source + * @description URL redirection based on unvalidated user-input + * may cause redirection to malicious web sites. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/unvalidated-url-redirection-local + * @tags security + * external/cwe/cwe-601 + */ +import java +import semmle.code.java.dataflow.FlowSources +import UrlRedirect + +class UrlRedirectLocalConfig extends TaintTracking::Configuration { + UrlRedirectLocalConfig() { this = "UrlRedirectLocalConfig" } + override predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput } + override predicate isSink(DataFlow::Node sink) { sink instanceof UrlRedirectSink } +} + +from UrlRedirectSink sink, LocalUserInput source, UrlRedirectLocalConfig conf +where conf.hasFlow(source, sink) +select sink, "Potentially untrusted URL redirection due to $@.", + source, "user-provided value" diff --git a/java/ql/src/Security/CWE/CWE-611/XXE.qhelp b/java/ql/src/Security/CWE/CWE-611/XXE.qhelp new file mode 100644 index 00000000000..b9537ba8f09 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-611/XXE.qhelp @@ -0,0 +1,70 @@ + + + + +

    +Parsing untrusted XML files with a weakly configured XML parser may lead to an XML External Entity (XXE) attack. This type of attack +uses external entity references to access arbitrary files on a system, carry out denial of service, or server side +request forgery. Even when the result of parsing is not returned to the user, out-of-band +data retrieval techniques may allow attackers to steal sensitive data. Denial of services can also be +carried out in this situation. +

    +

    +There are many XML parsers for Java, and most of them are vulnerable to XXE because their default settings enable parsing of +external entities. This query currently identifies vulnerable XML parsing from the following parsers: javax.xml.parsers.DocumentBuilder, +javax.xml.stream.XMLStreamReader, org.jdom.input.SAXBuilder/org.jdom2.input.SAXBuilder, +javax.xml.parsers.SAXParser,org.dom4j.io.SAXReader, org.xml.sax.XMLReader, +javax.xml.transform.sax.SAXSource, javax.xml.transform.TransformerFactory, +javax.xml.transform.sax.SAXTransformerFactory, javax.xml.validation.SchemaFactory, +javax.xml.bind.Unmarshaller and javax.xml.xpath.XPathExpression. +

    +
    + + +

    +The best way to prevent XXE attacks is to disable the parsing of any Document Type Declarations (DTDs) in untrusted data. +If this is not possible you should disable the parsing of external general entities and external parameter entities. +This improves security but the code will still be at risk of denial of service and server side request forgery attacks. +

    +
    + + +

    +The following example calls parse on a DocumentBuilder that is not safely configured on +untrusted data, and is therefore inherently unsafe. +

    + + +

    +In this example, the DocumentBuilder is created with DTD disabled, securing it against XXE attack. +

    + + +
    + + + +
  • +OWASP vulnerability description: +XML External Entity (XXE) Processing. +
  • +
  • +OWASP guidance on parsing xml files: +XXE Prevention Cheat Sheet. +
  • +
  • +Paper by Timothy Morgen: +XML Schema, DTD, and Entity Attacks +
  • +
  • +Out-of-band data retrieval: Timur Yunusov & Alexey Osipov, Black hat EU 2013: +XML Out-Of-Band Data Retrieval. +
  • +
  • +Denial of service attack (Billion laughs): +Billion Laughs. +
  • + +
    + +
    diff --git a/java/ql/src/Security/CWE/CWE-611/XXE.ql b/java/ql/src/Security/CWE/CWE-611/XXE.ql new file mode 100644 index 00000000000..8582a15cc84 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-611/XXE.ql @@ -0,0 +1,42 @@ +/** + * @name Resolving XML external entity in user-controlled data + * @description Parsing user-controlled XML documents and allowing expansion of external entity + * references may lead to disclosure of confidential data or denial of service. + * @kind problem + * @problem.severity error + * @precision high + * @id java/xxe + * @tags security + * external/cwe/cwe-611 + */ + +import java +import XmlParsers +import semmle.code.java.dataflow.FlowSources + +class SafeSAXSourceFlowConfig extends TaintTracking::Configuration2 { + SafeSAXSourceFlowConfig() { this = "XmlParsers::SafeSAXSourceFlowConfig" } + override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SafeSAXSource } + override predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(XmlParserCall parse).getSink() } + override int fieldFlowBranchLimit() { result = 0 } +} + +class UnsafeXxeSink extends DataFlow::ExprNode { + UnsafeXxeSink() { + not exists(SafeSAXSourceFlowConfig safeSource | safeSource.hasFlowTo(this)) and + exists(XmlParserCall parse | + parse.getSink() = this.getExpr() and + not parse.isSafe() + ) + } +} + +class XxeConfig extends TaintTracking::Configuration { + XxeConfig() { this = "XXE.ql::XxeConfig" } + override predicate isSource(DataFlow::Node src) { src instanceof RemoteUserInput } + override predicate isSink(DataFlow::Node sink) { sink instanceof UnsafeXxeSink } +} + +from UnsafeXxeSink sink, RemoteUserInput source, XxeConfig conf +where conf.hasFlow(source, sink) +select sink, "Unsafe parsing of XML file from $@.", source, "user input" diff --git a/java/ql/src/Security/CWE/CWE-611/XXEBad.java b/java/ql/src/Security/CWE/CWE-611/XXEBad.java new file mode 100644 index 00000000000..7b7baeadfda --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-611/XXEBad.java @@ -0,0 +1,5 @@ +public void parse(Socket sock) throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + builder.parse(sock.getInputStream()); //unsafe +} diff --git a/java/ql/src/Security/CWE/CWE-611/XXEGood.java b/java/ql/src/Security/CWE/CWE-611/XXEGood.java new file mode 100644 index 00000000000..f1eef22e62a --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-611/XXEGood.java @@ -0,0 +1,6 @@ +public void disableDTDParse(Socket sock) throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + DocumentBuilder builder = factory.newDocumentBuilder(); + builder.parse(sock.getInputStream()); //safe +} diff --git a/java/ql/src/Security/CWE/CWE-611/XmlParsers.qll b/java/ql/src/Security/CWE/CWE-611/XmlParsers.qll new file mode 100644 index 00000000000..2c4736ebaa4 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-611/XmlParsers.qll @@ -0,0 +1,1108 @@ +/** Provides classes and predicates for modeling XML parsers in Java. */ + +import java +import semmle.code.java.dataflow.DataFlow +import semmle.code.java.dataflow.DataFlow2 +import semmle.code.java.dataflow.DataFlow3 +import semmle.code.java.dataflow.DataFlow4 +import semmle.code.java.dataflow.DataFlow5 +private import semmle.code.java.dataflow.SSA + +/* + * Various XML parsers in Java. + */ + +/** + * An abstract type representing a call to parse XML files. + */ +abstract class XmlParserCall extends MethodAccess { + /** + * Gets the argument representing the XML content to be parsed. + */ + abstract Expr getSink(); + /** + * Holds if the call is safe. + */ + abstract predicate isSafe(); +} + +/** + * An access to a method use for configuring the parser. + */ +abstract class ParserConfig extends MethodAccess { + + /** + * Holds if the method disables a property. + */ + predicate disables(Expr e) { + this.getArgument(0) = e and + this.getArgument(1).(BooleanLiteral).getBooleanValue() = false + } + + /** + * Holds if the method enables a property. + */ + predicate enables(Expr e) { + this.getArgument(0) = e and + this.getArgument(1).(BooleanLiteral).getBooleanValue() = true + } +} + +/* + * https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#DocumentBuilder + */ + +/** The class `javax.xml.parsers.DocumentBuilderFactory`. */ +class DocumentBuilderFactory extends RefType { + DocumentBuilderFactory() { + this.hasQualifiedName("javax.xml.parsers", "DocumentBuilderFactory") + } +} + +/** The class `javax.xml.parsers.DocumentBuilder`. */ +class DocumentBuilder extends RefType { + DocumentBuilder() { + this.hasQualifiedName("javax.xml.parsers", "DocumentBuilder") + } +} + +/** A call to `DocumentBuilder.parse`. */ +class DocumentBuilderParse extends XmlParserCall { + DocumentBuilderParse() { + exists(Method m | + this.getMethod() = m and + m.getDeclaringType() instanceof DocumentBuilder and + m.hasName("parse") + ) + } + + override Expr getSink() { + result = this.getArgument(0) + } + + override predicate isSafe() { + exists(SafeDocumentBuilderToDocumentBuilderParseFlowConfig conf | conf.hasFlowToExpr(this.getQualifier())) + } +} + +private class SafeDocumentBuilderToDocumentBuilderParseFlowConfig extends DataFlow2::Configuration { + SafeDocumentBuilderToDocumentBuilderParseFlowConfig() { this = "XmlParsers::SafeDocumentBuilderToDocumentBuilderParseFlowConfig" } + override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SafeDocumentBuilder } + override predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(DocumentBuilderParse dbp).getQualifier() } + override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + exists(RefType t, ReturnStmt ret, Method m | + node2.asExpr().(ClassInstanceExpr).getConstructedType().getSourceDeclaration() = t and + t.getASourceSupertype+().hasQualifiedName("java.lang", "ThreadLocal") and + ret.getResult() = node1.asExpr() and + ret.getEnclosingCallable() = m and + m.hasName("initialValue") and + m.getDeclaringType() = t + ) or + exists(MethodAccess ma, Method m | + ma = node2.asExpr() and + ma.getQualifier() = node1.asExpr() and + ma.getMethod() = m and + m.hasName("get") and + m.getDeclaringType().getSourceDeclaration().hasQualifiedName("java.lang", "ThreadLocal") + ) + } + override int fieldFlowBranchLimit() { result = 0 } +} + +/** + * A `ParserConfig` specific to `DocumentBuilderFactory`. + */ +class DocumentBuilderFactoryConfig extends ParserConfig { + DocumentBuilderFactoryConfig() { + exists(Method m | + m = this.getMethod() and + m.getDeclaringType() instanceof DocumentBuilderFactory and + m.hasName("setFeature") + ) + } +} + +private predicate constantStringExpr(Expr e, string val) { + e.(CompileTimeConstantExpr).getStringValue() = val or + exists(SsaExplicitUpdate v, Expr src | + e = v.getAUse() and + src = v.getDefiningExpr().(VariableAssign).getSource() and + constantStringExpr(src, val) + ) +} + +/** An expression that always has the same string value. */ +private class ConstantStringExpr extends Expr { + string value; + ConstantStringExpr() { + constantStringExpr(this, value) + } + + /** Get the string value of this expression. */ + string getStringValue() { + result = value + } +} + +/** + * A general configuration that is safe when enabled. + */ +Expr singleSafeConfig() { + result.(ConstantStringExpr).getStringValue() = "http://apache.org/xml/features/disallow-doctype-decl" or + result.(ConstantStringExpr).getStringValue() = "http://javax.xml.XMLConstants/feature/secure-processing" or + exists(Field f | + result = f.getAnAccess() and + f.hasName("FEATURE_SECURE_PROCESSING") and + f.getDeclaringType().hasQualifiedName("javax.xml", "XMLConstants") + ) +} + +/** + * A safely configured `DocumentBuilderFactory` that is safe for creating `DocumentBuilder`. + */ +class SafeDocumentBuilderFactory extends VarAccess { + SafeDocumentBuilderFactory() { + exists(Variable v | v = this.getVariable() | + exists(DocumentBuilderFactoryConfig config | config.getQualifier() = v.getAnAccess() | + config.enables(singleSafeConfig()) + ) + or + ( + //These two need to be set together to work + exists(DocumentBuilderFactoryConfig config | config.getQualifier() = v.getAnAccess() | + config.disables(any(ConstantStringExpr s | s.getStringValue() = "http://xml.org/sax/features/external-general-entities")) + ) and + exists(DocumentBuilderFactoryConfig config | config.getQualifier() = v.getAnAccess() | + config.disables(any(ConstantStringExpr s | s.getStringValue() = "http://xml.org/sax/features/external-parameter-entities")) + ) + ) + ) + } +} + +private class DocumentBuilderConstruction extends MethodAccess { + DocumentBuilderConstruction() { + exists(Method m | + this.getMethod() = m and + m.getDeclaringType() instanceof DocumentBuilderFactory and + m.hasName("newDocumentBuilder") + ) + } +} + +private class SafeDocumentBuilderFactoryToDocumentBuilderConstructionFlowConfig extends DataFlow3::Configuration { + SafeDocumentBuilderFactoryToDocumentBuilderConstructionFlowConfig() { this = "XmlParsers::SafeDocumentBuilderFactoryToDocumentBuilderConstructionFlowConfig" } + override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SafeDocumentBuilderFactory } + override predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(DocumentBuilderConstruction dbc).getQualifier() } + override int fieldFlowBranchLimit() { result = 0 } +} + +/** + * A `DocumentBuilder` created from a safely configured `DocumentBuilderFactory`. + */ +class SafeDocumentBuilder extends DocumentBuilderConstruction { + SafeDocumentBuilder() { + exists(SafeDocumentBuilderFactoryToDocumentBuilderConstructionFlowConfig conf | + conf.hasFlowToExpr(this.getQualifier()) + ) + } +} + +/* + * https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#XMLInputFactory_.28a_StAX_parser.29 + */ + +/** The class `javax.xml.stream.XMLInputFactory`. */ +class XmlInputFactory extends RefType { + XmlInputFactory() { + this.hasQualifiedName("javax.xml.stream", "XMLInputFactory") + } +} + +/** A call to `XMLInputFactory.createXMLStreamReader`. */ +class XmlInputFactoryStreamReader extends XmlParserCall { + XmlInputFactoryStreamReader() { + exists(Method m | + this.getMethod() = m and + m.getDeclaringType() instanceof XmlInputFactory and + m.hasName("createXMLStreamReader") + ) + } + + override Expr getSink() { + if this.getMethod().getParameterType(0) instanceof TypeString + then + result = this.getArgument(1) + else + result = this.getArgument(0) + } + + override predicate isSafe() { + exists(SafeXmlInputFactoryToXmlInputFactoryReaderFlowConfig conf | conf.hasFlowToExpr(this.getQualifier())) + } +} + +private class SafeXmlInputFactoryToXmlInputFactoryReaderFlowConfig extends DataFlow2::Configuration { + SafeXmlInputFactoryToXmlInputFactoryReaderFlowConfig() { this = "XmlParsers::SafeXmlInputFactoryToXmlInputFactoryReaderFlowConfig" } + override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SafeXmlInputFactory } + override predicate isSink(DataFlow::Node sink) { + sink.asExpr() = any(XmlInputFactoryStreamReader xifsr).getQualifier() or + sink.asExpr() = any(XmlInputFactoryEventReader xifer).getQualifier() + } + override int fieldFlowBranchLimit() { result = 0 } +} + +/** A call to `XMLInputFactory.createEventReader`. */ +class XmlInputFactoryEventReader extends XmlParserCall { + XmlInputFactoryEventReader() { + exists(Method m | + this.getMethod() = m and + m.getDeclaringType() instanceof XmlInputFactory and + m.hasName("createXMLEventReader") + ) + } + + override Expr getSink() { + if this.getMethod().getParameterType(0) instanceof TypeString + then + result = this.getArgument(1) + else + result = this.getArgument(0) + } + + override predicate isSafe() { + exists(SafeXmlInputFactoryToXmlInputFactoryReaderFlowConfig conf | conf.hasFlowToExpr(this.getQualifier())) + } +} + +/** + * A `ParserConfig` specific to `XMLInputFactory`. + */ +class XmlInputFactoryConfig extends ParserConfig { + XmlInputFactoryConfig() { + exists(Method m | + m = this.getMethod() and + m.getDeclaringType() instanceof XmlInputFactory and + m.hasName("setProperty") + ) + } +} + +/** + * An `XmlInputFactory` specific expression that indicates whether parsing external entities is supported. + */ +Expr configOptionIsSupportingExternalEntities() { + result.(ConstantStringExpr).getStringValue() = "javax.xml.stream.isSupportingExternalEntities" or + exists(Field f | + result = f.getAnAccess() and + f.hasName("IS_SUPPORTING_EXTERNAL_ENTITIES") and + f.getDeclaringType() instanceof XmlInputFactory + ) +} + +/** + * An `XmlInputFactory` specific expression that indicates whether DTD is supported. + */ +Expr configOptionSupportDTD() { + result.(ConstantStringExpr).getStringValue() = "javax.xml.stream.supportDTD" or + exists(Field f | + result = f.getAnAccess() and + f.hasName("SUPPORT_DTD") and + f.getDeclaringType() instanceof XmlInputFactory + ) +} + +/** + * A safely configured `XmlInputFactory`. + */ +class SafeXmlInputFactory extends VarAccess { + SafeXmlInputFactory() { + exists(Variable v | + v = this.getVariable() and + exists(XmlInputFactoryConfig config | config.getQualifier() = v.getAnAccess() | + config.disables(configOptionIsSupportingExternalEntities()) + ) and + exists(XmlInputFactoryConfig config | config.getQualifier() = v.getAnAccess() | + config.disables(configOptionSupportDTD()) + ) + ) + } +} + + +/* + * https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#SAXBuilder + */ + +/** + * The class `org.jdom.input.SAXBuilder.` + */ +class SAXBuilder extends RefType { + SAXBuilder() { + this.hasQualifiedName("org.jdom.input", "SAXBuilder") or + this.hasQualifiedName("org.jdom2.input", "SAXBuilder") + } +} + +/** + * A call to `SAXBuilder.build.` + */ +class SAXBuilderParse extends XmlParserCall { + SAXBuilderParse() { + exists(Method m | + m = this.getMethod() and + m.getDeclaringType() instanceof SAXBuilder and + m.hasName("build") + ) + } + + override Expr getSink() { + result = this.getArgument(0) + } + + override predicate isSafe() { + exists(SafeSAXBuilderToSAXBuilderParseFlowConfig conf | conf.hasFlowToExpr(this.getQualifier())) + } +} + +private class SafeSAXBuilderToSAXBuilderParseFlowConfig extends DataFlow2::Configuration { + SafeSAXBuilderToSAXBuilderParseFlowConfig() { this = "XmlParsers::SafeSAXBuilderToSAXBuilderParseFlowConfig" } + override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SafeSAXBuilder } + override predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(SAXBuilderParse sax).getQualifier() } + override int fieldFlowBranchLimit() { result = 0 } +} + +/** + * A `ParserConfig` specific to `SAXBuilder`. + */ +class SAXBuilderConfig extends ParserConfig { + SAXBuilderConfig() { + exists(Method m | + m = this.getMethod() and + m.getDeclaringType() instanceof SAXBuilder and + m.hasName("setFeature") + ) + } +} + +/** A safely configured `SAXBuilder`. */ +class SafeSAXBuilder extends VarAccess { + SafeSAXBuilder() { + exists(Variable v | + v = this.getVariable() and + exists(SAXBuilderConfig config | config.getQualifier() = v.getAnAccess() | + config.enables(any(ConstantStringExpr s | s.getStringValue() = "http://apache.org/xml/features/disallow-doctype-decl")) + ) + ) + } +} + +/* + * The case in + * https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#Unmarshaller + * will be split into two, one covers a SAXParser as a sink, the other the SAXSource as a sink. + */ + +/** + * The class `javax.xml.parsers.SAXParser`. + */ +class SAXParser extends RefType { + SAXParser() { + this.hasQualifiedName("javax.xml.parsers", "SAXParser") + } +} + +/** The class `javax.xml.parsers.SAXParserFactory`. */ +class SAXParserFactory extends RefType { + SAXParserFactory() { + this.hasQualifiedName("javax.xml.parsers", "SAXParserFactory") + } +} + +/** A call to `SAXParser.parse`. */ +class SAXParserParse extends XmlParserCall { + SAXParserParse() { + exists(Method m | + m = this.getMethod() and + m.getDeclaringType() instanceof SAXParser and + m.hasName("parse") + ) + } + + override Expr getSink() { + result = this.getArgument(0) + } + + override predicate isSafe() { + exists(SafeSAXParserFlowConfig sp | sp.hasFlowToExpr(this.getQualifier())) + } +} + +/** A `ParserConfig` that is specific to `SAXParserFactory`. */ +class SAXParserFactoryConfig extends ParserConfig { + SAXParserFactoryConfig() { + exists(Method m | + m = this.getMethod() and + m.getDeclaringType() instanceof SAXParserFactory and + m.hasName("setFeature") + ) + } +} + + +/** + * A safely configured `SAXParserFactory`. + */ +class SafeSAXParserFactory extends VarAccess { + SafeSAXParserFactory() { + exists(Variable v | v = this.getVariable() | + exists(SAXParserFactoryConfig config | config.getQualifier() = v.getAnAccess() | + config.disables(any(ConstantStringExpr s | s.getStringValue() = "http://xml.org/sax/features/external-general-entities")) + ) and + exists(SAXParserFactoryConfig config | config.getQualifier() = v.getAnAccess() | + config.disables(any(ConstantStringExpr s | s.getStringValue() = "http://xml.org/sax/features/external-parameter-entities")) + ) and + exists(SAXParserFactoryConfig config | config.getQualifier() = v.getAnAccess() | + config.disables(any(ConstantStringExpr s | s.getStringValue() = "http://apache.org/xml/features/nonvalidating/load-external-dtd")) + ) + ) + } +} + +private class SafeSAXParserFactoryToNewSAXParserFlowConfig extends DataFlow5::Configuration { + SafeSAXParserFactoryToNewSAXParserFlowConfig() { this = "XmlParsers::SafeSAXParserFactoryToNewSAXParserFlowConfig" } + override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SafeSAXParserFactory } + override predicate isSink(DataFlow::Node sink) { + exists(MethodAccess ma, Method m | + sink.asExpr() = ma.getQualifier() and + ma.getMethod() = m and + m.getDeclaringType() instanceof SAXParserFactory and + m.hasName("newSAXParser") + ) + } + override int fieldFlowBranchLimit() { result = 0 } +} + +private class SafeSAXParserFlowConfig extends DataFlow4::Configuration { + SafeSAXParserFlowConfig() { this = "XmlParsers::SafeSAXParserFlowConfig" } + override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SafeSAXParser } + override predicate isSink(DataFlow::Node sink) { + exists(MethodAccess ma | sink.asExpr() = ma.getQualifier() and ma.getMethod().getDeclaringType() instanceof SAXParser) + } + override int fieldFlowBranchLimit() { result = 0 } +} + +/** A `SAXParser` created from a safely configured `SAXParserFactory`. */ +class SafeSAXParser extends MethodAccess { + SafeSAXParser() { + exists(SafeSAXParserFactoryToNewSAXParserFlowConfig sdf | + this.getMethod().getDeclaringType() instanceof SAXParserFactory and + this.getMethod().hasName("newSAXParser") and + sdf.hasFlowToExpr(this.getQualifier()) + ) + } +} + +/* SAXReader: https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#SAXReader */ + +/** + * The class `org.dom4j.io.SAXReader`. + */ +class SAXReader extends RefType { + SAXReader() { + this.hasQualifiedName("org.dom4j.io", "SAXReader") + } +} + +/** A call to `SAXReader.read`. */ +class SAXReaderRead extends XmlParserCall { + SAXReaderRead() { + exists(Method m | + m = this.getMethod() and + m.getDeclaringType() instanceof SAXReader and + m.hasName("read") + ) + } + + override Expr getSink() { + result = this.getArgument(0) + } + + override predicate isSafe() { + exists(SafeSAXReaderFlowConfig sr | sr.hasFlowToExpr(this.getQualifier())) + } +} + +/** A `ParserConfig` specific to `SAXReader`. */ +class SAXReaderConfig extends ParserConfig { + SAXReaderConfig() { + exists(Method m | + m = this.getMethod() and + m.getDeclaringType() instanceof SAXReader and + m.hasName("setFeature") + ) + } +} + +private class SafeSAXReaderFlowConfig extends DataFlow4::Configuration { + SafeSAXReaderFlowConfig() { this = "XmlParsers::SafeSAXReaderFlowConfig" } + override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SafeSAXReader } + override predicate isSink(DataFlow::Node sink) { + exists(MethodAccess ma | sink.asExpr() = ma.getQualifier() and ma.getMethod().getDeclaringType() instanceof SAXReader) + } + override int fieldFlowBranchLimit() { result = 0 } +} + +/** A safely configured `SAXReader`. */ +class SafeSAXReader extends VarAccess { + SafeSAXReader() { + exists(Variable v | v = this.getVariable() | + exists(SAXReaderConfig config | config.getQualifier() = v.getAnAccess() | + config.disables(any(ConstantStringExpr s | s.getStringValue() = "http://xml.org/sax/features/external-general-entities")) + ) and + exists(SAXReaderConfig config | config.getQualifier() = v.getAnAccess() | + config.disables(any(ConstantStringExpr s | s.getStringValue() = "http://xml.org/sax/features/external-parameter-entities")) + ) and + exists(SAXReaderConfig config | config.getQualifier() = v.getAnAccess() | + config.enables(any(ConstantStringExpr s | s.getStringValue() = "http://apache.org/xml/features/disallow-doctype-decl")) + ) + ) + } +} + +/* https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#XMLReader */ + +/** The class `org.xml.sax.XMLReader`. */ +class XMLReader extends RefType { + XMLReader() { + this.hasQualifiedName("org.xml.sax", "XMLReader") + } +} + +/** A call to `XMLReader.read`. */ +class XMLReaderParse extends XmlParserCall { + XMLReaderParse() { + exists(Method m | + m = this.getMethod() and + m.getDeclaringType() instanceof XMLReader and + m.hasName("parse") + ) + } + + override Expr getSink() { + result = this.getArgument(0) + } + + override predicate isSafe() { + exists(ExplicitlySafeXMLReader sr | sr.flowsTo(this.getQualifier())) or + exists(CreatedSafeXMLReader cr | cr.flowsTo(this.getQualifier())) + } +} + +/** A `ParserConfig` specific to the `XMLReader`. */ +class XMLReaderConfig extends ParserConfig { + XMLReaderConfig() { + exists(Method m | + m = this.getMethod() and + m.getDeclaringType() instanceof XMLReader and + m.hasName("setFeature") + ) + } +} + +private class ExplicitlySafeXMLReaderFlowConfig extends DataFlow3::Configuration { + ExplicitlySafeXMLReaderFlowConfig() { this = "XmlParsers::ExplicitlySafeXMLReaderFlowConfig" } + override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof ExplicitlySafeXMLReader } + override predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof SafeXMLReaderFlowSink } + override int fieldFlowBranchLimit() { result = 0 } +} + +class SafeXMLReaderFlowSink extends Expr { + SafeXMLReaderFlowSink() { + this = any(XMLReaderParse p).getQualifier() or + this = any(ConstructedSAXSource s).getArgument(0) or + this = any(SAXSourceSetReader s).getArgument(0) + } +} + +/** An `XMLReader` that is explicitly configured to be safe. */ +class ExplicitlySafeXMLReader extends VarAccess { + ExplicitlySafeXMLReader() { + exists(Variable v | v = this.getVariable() | + ( + exists(XMLReaderConfig config | config.getQualifier() = v.getAnAccess() | + config.disables(any(ConstantStringExpr s | s.getStringValue() = "http://xml.org/sax/features/external-general-entities")) + ) and + exists(XMLReaderConfig config | config.getQualifier() = v.getAnAccess() | + config.disables(any(ConstantStringExpr s | s.getStringValue() = "http://xml.org/sax/features/external-parameter-entities")) + ) and + exists(XMLReaderConfig config | config.getQualifier() = v.getAnAccess() | + config.disables(any(ConstantStringExpr s | s.getStringValue() = "http://apache.org/xml/features/nonvalidating/load-external-dtd")) + ) + ) or + exists(XMLReaderConfig config | config.getQualifier() = v.getAnAccess() | + config.enables(any(ConstantStringExpr s | s.getStringValue() = "http://apache.org/xml/features/disallow-doctype-decl"))) + ) + } + predicate flowsTo(SafeXMLReaderFlowSink sink) { + any(ExplicitlySafeXMLReaderFlowConfig conf).hasFlow(DataFlow::exprNode(this), DataFlow::exprNode(sink)) + } +} + +private class CreatedSafeXMLReaderFlowConfig extends DataFlow3::Configuration { + CreatedSafeXMLReaderFlowConfig() { this = "XmlParsers::CreatedSafeXMLReaderFlowConfig" } + override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof CreatedSafeXMLReader } + override predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof SafeXMLReaderFlowSink } + override int fieldFlowBranchLimit() { result = 0 } +} + +/** An `XMLReader` that is obtained from a safe source. */ +class CreatedSafeXMLReader extends MethodAccess { + CreatedSafeXMLReader() { + //Obtained from SAXParser + exists(SafeSAXParserFlowConfig safeParser | + this.getMethod().getDeclaringType() instanceof SAXParser and + this.getMethod().hasName("getXMLReader") and + safeParser.hasFlowToExpr(this.getQualifier()) + ) or + //Obtained from SAXReader + exists(SafeSAXReaderFlowConfig safeReader | + this.getMethod().getDeclaringType() instanceof SAXReader and + this.getMethod().hasName("getXMLReader") and + safeReader.hasFlowToExpr(this.getQualifier()) + ) + } + predicate flowsTo(SafeXMLReaderFlowSink sink) { + any(CreatedSafeXMLReaderFlowConfig conf).hasFlow(DataFlow::exprNode(this), DataFlow::exprNode(sink)) + } +} + +/* + * SAXSource in + * https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#Unmarshaller + */ + +/** The class `javax.xml.transform.sax.SAXSource` */ +class SAXSource extends RefType { + SAXSource() { + this.hasQualifiedName("javax.xml.transform.sax", "SAXSource") + } +} + +/** A call to the constructor of `SAXSource` with `XMLReader` and `InputSource`. */ +class ConstructedSAXSource extends ClassInstanceExpr { + ConstructedSAXSource() { + this.getConstructedType() instanceof SAXSource and + this.getNumArgument() = 2 and + this.getArgument(0).getType() instanceof XMLReader + } + /** + * Gets the argument representing the XML content to be parsed. + */ + Expr getSink() { + result = this.getArgument(1) + } + /** Holds if the resulting `SAXSource` is safe. */ + predicate isSafe() { + exists(CreatedSafeXMLReader safeReader | safeReader.flowsTo(this.getArgument(0))) or + exists(ExplicitlySafeXMLReader safeReader | safeReader.flowsTo(this.getArgument(0))) + } +} + +/** A call to the `SAXSource.setXMLReader` method. */ +class SAXSourceSetReader extends MethodAccess { + SAXSourceSetReader() { + exists(Method m | + m = this.getMethod() and + m.getDeclaringType() instanceof SAXSource and + m.hasName("setXMLReader") + ) + } +} + +/** A `SAXSource` that is safe to use. */ +class SafeSAXSource extends Expr { + SafeSAXSource() { + exists(Variable v | v = this.(VarAccess).getVariable() | + exists(SAXSourceSetReader s | s.getQualifier() = v.getAnAccess() | + ( + exists(CreatedSafeXMLReader safeReader | safeReader.flowsTo(s.getArgument(0))) or + exists(ExplicitlySafeXMLReader safeReader | safeReader.flowsTo(s.getArgument(0))) + ) + ) + ) or + this.(ConstructedSAXSource).isSafe() + } +} + +/* Transformer: https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#TransformerFactory */ + +/** An access to a method use for configuring a transformer or schema. */ +abstract class TransformerConfig extends MethodAccess { + /** Holds if the configuration is disabled */ + predicate disables(Expr e) { + this.getArgument(0) = e and + this.getArgument(1).(StringLiteral).getValue() = "" + } +} + +/** The class `javax.xml.XMLConstants`. */ +class XmlConstants extends RefType { + XmlConstants() { + this.hasQualifiedName("javax.xml", "XMLConstants") + } +} + +/** A configuration specific for transformers and schema. */ +Expr configAccessExternalDTD() { + result.(ConstantStringExpr).getStringValue() = "http://javax.xml.XMLConstants/property/accessExternalDTD" or + exists(Field f | + result = f.getAnAccess() and + f.hasName("ACCESS_EXTERNAL_DTD") and + f.getDeclaringType() instanceof XmlConstants + ) +} + +/** A configuration specific for transformers. */ +Expr configAccessExternalStyleSheet() { + result.(ConstantStringExpr).getStringValue() = "http://javax.xml.XMLConstants/property/accessExternalStylesheet" or + exists(Field f | + result = f.getAnAccess() and + f.hasName("ACCESS_EXTERNAL_STYLESHEET") and + f.getDeclaringType() instanceof XmlConstants + ) +} + +/** A configuration specific for schema. */ +Expr configAccessExternalSchema() { + result.(ConstantStringExpr).getStringValue() = "http://javax.xml.XMLConstants/property/accessExternalSchema" or + exists(Field f | + result = f.getAnAccess() and + f.hasName("ACCESS_EXTERNAL_SCHEMA") and + f.getDeclaringType() instanceof XmlConstants + ) +} + +/** The class `javax.xml.transform.TransformerFactory` or `javax.xml.transform.sax.SAXTransformerFactory`. */ +class TransformerFactory extends RefType { + TransformerFactory() { + this.hasQualifiedName("javax.xml.transform", "TransformerFactory") + or + this.hasQualifiedName("javax.xml.transform.sax", "SAXTransformerFactory") + } +} + +/** The class `javax.xml.transform.Transformer`. */ +class Transformer extends RefType { + Transformer() { + this.hasQualifiedName("javax.xml.transform", "Transformer") + } +} + +/** A call to `Transformer.transform`. */ +class TransformerTransform extends XmlParserCall { + TransformerTransform() { + exists(Method m | + this.getMethod() = m and + m.getDeclaringType() instanceof Transformer and + m.hasName("transform") + ) + } + + override Expr getSink() { + result = this.getArgument(0) + } + + override predicate isSafe() { + exists(SafeTransformerToTransformerTransformFlowConfig st | st.hasFlowToExpr(this.getQualifier())) + } +} + +private class SafeTransformerToTransformerTransformFlowConfig extends DataFlow2::Configuration { + SafeTransformerToTransformerTransformFlowConfig() { this = "XmlParsers::SafeTransformerToTransformerTransformFlowConfig" } + override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SafeTransformer } + override predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(TransformerTransform tt).getQualifier() } + override int fieldFlowBranchLimit() { result = 0 } +} + +/** A call to `Transformer.newTransformer` with source. */ +class TransformerFactorySource extends XmlParserCall { + TransformerFactorySource() { + exists(Method m | + this.getMethod() = m and + m.getDeclaringType() instanceof TransformerFactory and + m.hasName("newTransformer") + ) + } + + override Expr getSink() { + result = this.getArgument(0) + } + + override predicate isSafe() { + exists(SafeTransformerFactoryFlowConfig stf | stf.hasFlowToExpr(this.getQualifier())) + } +} + +/** A `ParserConfig` specific to `TransformerFactory`. */ +class TransformerFactoryConfig extends TransformerConfig { + TransformerFactoryConfig() { + exists(Method m | + m = this.getMethod() and + m.getDeclaringType() instanceof TransformerFactory and + m.hasName("setAttribute") + ) + } +} + +private class SafeTransformerFactoryFlowConfig extends DataFlow3::Configuration { + SafeTransformerFactoryFlowConfig() { this = "XmlParsers::SafeTransformerFactoryFlowConfig" } + override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SafeTransformerFactory } + override predicate isSink(DataFlow::Node sink) { + exists(MethodAccess ma | sink.asExpr() = ma.getQualifier() and ma.getMethod().getDeclaringType() instanceof TransformerFactory) + } + override int fieldFlowBranchLimit() { result = 0 } +} + +/** A safely configured `TransformerFactory`. */ +class SafeTransformerFactory extends VarAccess { + SafeTransformerFactory() { + exists(Variable v | v = this.getVariable() | + exists(TransformerFactoryConfig config | config.getQualifier() = v.getAnAccess() | + config.disables(configAccessExternalDTD()) + ) and + exists(TransformerFactoryConfig config | config.getQualifier() = v.getAnAccess() | + config.disables(configAccessExternalStyleSheet()) + ) + ) + } +} + +/** A `Transformer` created from a safely configured `TranformerFactory`. */ +class SafeTransformer extends MethodAccess { + SafeTransformer() { + exists(SafeTransformerFactoryFlowConfig stf, Method m | + this.getMethod() = m and + m.getDeclaringType() instanceof TransformerFactory and + m.hasName("newTransformer") and + stf.hasFlowToExpr(this.getQualifier()) + ) + } +} + +/* SAXTransformer: https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#SAXTransformerFactory + * Has an extra method called newFilter. + */ + +/** A call to `SAXTransformerFactory.newFilter`. */ +class SAXTransformerFactoryNewXMLFilter extends XmlParserCall { + SAXTransformerFactoryNewXMLFilter() { + exists(Method m | + this.getMethod() = m and + m.getDeclaringType().hasQualifiedName("javax.xml.transform.sax", "SAXTransformerFactory") and + m.hasName("newXMLFilter") + ) + } + + override Expr getSink() { + result = this.getArgument(0) + } + + override predicate isSafe() { + exists(SafeTransformerFactoryFlowConfig stf | stf.hasFlowToExpr(this.getQualifier())) + } +} + +/* Schema: https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#SchemaFactory */ +/** The class `javax.xml.validation.SchemaFactory`. */ +class SchemaFactory extends RefType { + SchemaFactory() { + this.hasQualifiedName("javax.xml.validation", "SchemaFactory") + } +} + +/** A `ParserConfig` specific to `SchemaFactory`. */ +class SchemaFactoryConfig extends TransformerConfig { + SchemaFactoryConfig() { + exists(Method m | + this.getMethod() = m and + m.getDeclaringType() instanceof SchemaFactory and + m.hasName("setProperty") + ) + } +} + +/** A call to `SchemaFactory.newSchema`. */ +class SchemaFactoryNewSchema extends XmlParserCall { + SchemaFactoryNewSchema() { + exists(Method m | + this.getMethod() = m and + m.getDeclaringType() instanceof SchemaFactory and + m.hasName("newSchema") + ) + } + + override Expr getSink() { + result = this.getArgument(0) + } + + override predicate isSafe() { + exists(SafeSchemaFactoryToSchemaFactoryNewSchemaFlowConfig ssf | ssf.hasFlowToExpr(this.getQualifier())) + } +} + +private class SafeSchemaFactoryToSchemaFactoryNewSchemaFlowConfig extends DataFlow2::Configuration { + SafeSchemaFactoryToSchemaFactoryNewSchemaFlowConfig() { this = "XmlParsers::SafeSchemaFactoryToSchemaFactoryNewSchemaFlowConfig" } + override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SafeSchemaFactory } + override predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(SchemaFactoryNewSchema sfns).getQualifier() } + override int fieldFlowBranchLimit() { result = 0 } +} + +/** A safely configured `SchemaFactory`. */ +class SafeSchemaFactory extends VarAccess { + SafeSchemaFactory() { + exists(Variable v | v = this.getVariable() | + exists(SchemaFactoryConfig config | config.getQualifier() = v.getAnAccess() | + config.disables(configAccessExternalDTD())) and + exists(SchemaFactoryConfig config | config.getQualifier() = v.getAnAccess() | + config.disables(configAccessExternalSchema())) + ) + } +} + +/* Unmarshaller: https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#Unmarshaller */ + +/** The class `javax.xml.bind.Unmarshaller`. */ +class XmlUnmarshaller extends RefType { + XmlUnmarshaller() { + this.hasQualifiedName("javax.xml.bind", "Unmarshaller") + } +} + +/** A call to `Unmarshaller.unmarshal`. */ +class XmlUnmarshal extends XmlParserCall { + XmlUnmarshal() { + exists(Method m | + this.getMethod() = m and + m.getDeclaringType() instanceof XmlUnmarshaller and + m.hasName("unmarshal") + ) + } + + override Expr getSink() { + result = this.getArgument(0) + } + + override predicate isSafe() { + none() + } +} + +/* XPathExpression: https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#XPathExpression */ + +/** The class `javax.xml.xpath.XPathExpression`. */ +class XPathExpression extends RefType { + XPathExpression() { + this.hasQualifiedName("javax.xml.xpath", "XPathExpression") + } +} + +/** A call to `XPathExpression.evaluate`. */ +class XPathEvaluate extends XmlParserCall { + XPathEvaluate() { + exists(Method m | + this.getMethod() = m and + m.getDeclaringType() instanceof XPathExpression and + m.hasName("evaluate") + ) + } + + override Expr getSink() { + result = this.getArgument(0) + } + + override predicate isSafe() { + none() + } +} + +// Sink methods in simplexml http://simple.sourceforge.net/home.php + +/** A call to `read` or `validate` in `Persister`. */ +class SimpleXMLPersisterCall extends XmlParserCall { + SimpleXMLPersisterCall() { + exists(Method m | + this.getMethod() = m and + (m.hasName("validate") or m.hasName("read")) and + m.getDeclaringType().hasQualifiedName("org.simpleframework.xml.core", "Persister") + ) + } + + override Expr getSink() { + result = this.getArgument(1) + } + + override predicate isSafe() { + none() + } +} + +/** A call to `provide` in `Provider`. */ +class SimpleXMLProviderCall extends XmlParserCall { + SimpleXMLProviderCall() { + exists(Method m | + this.getMethod() = m and + m.hasName("provide") and + ( + m.getDeclaringType().hasQualifiedName("org.simpleframework.xml.stream", "DocumentProvider") or + m.getDeclaringType().hasQualifiedName("org.simpleframework.xml.stream", "StreamProvider") + ) + ) + } + + override Expr getSink() { + result = this.getArgument(0) + } + + override predicate isSafe() { + none() + } +} + +/** A call to `read` in `NodeBuilder`. */ +class SimpleXMLNodeBuilderCall extends XmlParserCall { + SimpleXMLNodeBuilderCall() { + exists(Method m | + this.getMethod() = m and + m.hasName("read") and + m.getDeclaringType().hasQualifiedName("org.simpleframework.xml.stream", "NodeBuilder") + ) + } + + override Expr getSink() { + result = this.getArgument(0) + } + + override predicate isSafe() { + none() + } +} + +/** A call to the `format` method of the `Formatter`. */ +class SimpleXMLFormatterCall extends XmlParserCall { + SimpleXMLFormatterCall() { + exists(Method m | + this.getMethod() = m and + m.hasName("format") and + m.getDeclaringType().hasQualifiedName("org.simpleframework.xml.stream", "Formatter") + ) + } + + override Expr getSink() { + result = this.getArgument(0) + } + + override predicate isSafe() { + none() + } +} diff --git a/java/ql/src/Security/CWE/CWE-614/InsecureCookie.java b/java/ql/src/Security/CWE/CWE-614/InsecureCookie.java new file mode 100644 index 00000000000..8e8f47c23ca --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-614/InsecureCookie.java @@ -0,0 +1,16 @@ +public static void test(HttpServletRequest request, HttpServletResponse response) { + { + Cookie cookie = new Cookie("secret", "fakesecret"); + + // BAD: 'secure' flag not set + response.addCookie(cookie); + } + + { + Cookie cookie = new Cookie("secret", "fakesecret"); + + // GOOD: set 'secure' flag + cookie.setSecure(true); + response.addCookie(cookie); + } +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-614/InsecureCookie.qhelp b/java/ql/src/Security/CWE/CWE-614/InsecureCookie.qhelp new file mode 100644 index 00000000000..d49b0905de0 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-614/InsecureCookie.qhelp @@ -0,0 +1,37 @@ + + + +

    Failing to set the 'secure' flag on a cookie can cause it to be sent in cleartext. +This makes it easier for an attacker to intercept.

    + +
    + + +

    Always use setSecure to set the 'secure' flag on a cookie before adding it +to an HttpServletResponse.

    + +
    + + +

    This example shows two ways of adding a cookie to an HttpServletResponse. The first +way leaves out the setting of the 'secure' flag; the second way includes the setting of the flag.

    + + + +
    + + +
  • The CERT Oracle Secure Coding Standard for Java: + SER03-J. Do not serialize unencrypted, sensitive data.
  • +
  • Java 2 Platform Enterprise Edition, v5.0, API Specifications: +Class Cookie.
  • + + + + + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-614/InsecureCookie.ql b/java/ql/src/Security/CWE/CWE-614/InsecureCookie.ql new file mode 100644 index 00000000000..67ec8e02221 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-614/InsecureCookie.ql @@ -0,0 +1,24 @@ +/** + * @name Failure to use secure cookies + * @description Insecured cookies may be sent in cleartext, which makes them vulnerable to + * interception. + * @kind problem + * @problem.severity error + * @precision high + * @id java/insecure-cookie + * @tags security + * external/cwe/cwe-614 + */ +import java +import semmle.code.java.frameworks.Servlets + +from MethodAccess add +where + add.getMethod() instanceof ResponseAddCookieMethod and + not exists(Variable cookie, MethodAccess m | + add.getArgument(0) = cookie.getAnAccess() and + m.getMethod().getName() = "setSecure" and + m.getArgument(0).(BooleanLiteral).getBooleanValue() = true and + m.getQualifier() = cookie.getAnAccess() + ) +select add, "Cookie is added to response without the 'secure' flag being set." diff --git a/java/ql/src/Security/CWE/CWE-676/PotentiallyDangerousFunction.java b/java/ql/src/Security/CWE/CWE-676/PotentiallyDangerousFunction.java new file mode 100644 index 00000000000..ea5bb33564a --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-676/PotentiallyDangerousFunction.java @@ -0,0 +1,16 @@ +private volatile Thread blinker; + +public void stop() { + blinker = null; +} + +public void run() { + Thread thisThread = Thread.currentThread(); + while (blinker == thisThread) { + try { + Thread.sleep(interval); + } catch (InterruptedException e){ + } + repaint(); + } +} diff --git a/java/ql/src/Security/CWE/CWE-676/PotentiallyDangerousFunction.qhelp b/java/ql/src/Security/CWE/CWE-676/PotentiallyDangerousFunction.qhelp new file mode 100644 index 00000000000..aad17c95b39 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-676/PotentiallyDangerousFunction.qhelp @@ -0,0 +1,57 @@ + + + +

    This rule finds calls to methods that are dangerous to +use. Currently, it checks for calls +to Thread.stop.

    + +

    Stopping a thread with Thread.stop causes it to +receive a ThreadDeath exception. That exception +propagates up the stack, releasing all monitors that the thread was +holding. In some cases the relevant code will be protected by catching +the ThreadDeath exception and cleaning up, but because +the exception can potentially be thrown from so very many locations, +it is impractical to catch all such cases. As a result, +calling Thread.stop is likely to result in corrupt +data.

    + +
    + + +

    The best solution is usually to provide an alternate communication +mechanism for the thread that might need to be interrupted early. For +example, Oracle gives the following example of using a volatile +variable to communicate whether the worker thread should exit:

    + + + +

    It is also possible to use Thread.interrupt and to +catch and handle InterruptedException when it +occurs. However, it can be difficult to handle +an InterruptedException everywhere it might occur; for +example, the sample code above simply discards the exception rather +than actually exiting the thread.

    + +

    Another strategy is to use message passing, for example via +a BlockingQueue. In addition to passing the worker thread +its ordinary work via such a message queue, the worker can be asked to +exit by a particular kind of message being sent on the queue.

    + +
    + + +
  • The CERT Oracle Secure Coding Standard for Java: +THI05-J. Do +not use Thread.stop() to terminate threads.
  • +
  • Java SE +Documentation: Java +Thread Primitive Deprecation.
  • +
  • Java API: +Thread.interrupt, +BlockingQueue.
  • + + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-676/PotentiallyDangerousFunction.ql b/java/ql/src/Security/CWE/CWE-676/PotentiallyDangerousFunction.ql new file mode 100644 index 00000000000..fd61327b9c3 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-676/PotentiallyDangerousFunction.ql @@ -0,0 +1,23 @@ +/** + * @name Use of a potentially dangerous function + * @description Certain standard library routines are dangerous to call. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/potentially-dangerous-function + * @tags reliability + * security + * external/cwe/cwe-676 + */ +import java + +predicate dangerousMethod(string descriptor) { + descriptor = "java.lang.Thread.stop" +} + +from MethodAccess call, Method target, string descriptor +where + call.getCallee() = target and + descriptor = target.getDeclaringType().getQualifiedName() + "." + target.getName() and + dangerousMethod(descriptor) +select call, "Call to " + descriptor + " is potentially dangerous." diff --git a/java/ql/src/Security/CWE/CWE-681/NumericCastCommon.qll b/java/ql/src/Security/CWE/CWE-681/NumericCastCommon.qll new file mode 100644 index 00000000000..fffa0483801 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-681/NumericCastCommon.qll @@ -0,0 +1,68 @@ +import java +import semmle.code.java.arithmetic.Overflow +import semmle.code.java.dataflow.SSA +import semmle.code.java.controlflow.Guards +import semmle.code.java.dataflow.RangeAnalysis + +class NumericNarrowingCastExpr extends CastExpr { + NumericNarrowingCastExpr() { + exists(NumericType sourceType, NumericType targetType | + sourceType = getExpr().getType() and targetType = getType() + | + not targetType.(NumType).widerThanOrEqualTo(sourceType.(NumType)) + ) + } +} + +class RightShiftOp extends Expr { + RightShiftOp() { + this instanceof RShiftExpr or + this instanceof URShiftExpr or + this instanceof AssignRShiftExpr or + this instanceof AssignURShiftExpr + } + + private Expr getLhs() { + this.(BinaryExpr).getLeftOperand() = result or + this.(Assignment).getDest() = result + } + + Variable getShiftedVariable() { + getLhs() = result.getAnAccess() or + getLhs().getProperExpr().(AndBitwiseExpr).getAnOperand() = result.getAnAccess() + } +} + +predicate boundedRead(RValue read) { + exists(SsaVariable v, ConditionBlock cb, ComparisonExpr comp, boolean testIsTrue | + read = v.getAUse() and + cb.controls(read.getBasicBlock(), testIsTrue) and + cb.getCondition() = comp + | + comp.getLesserOperand() = v.getAUse() and testIsTrue = true or + comp.getGreaterOperand() = v.getAUse() and testIsTrue = false + ) +} + +predicate castCheck(RValue read) { + exists(EqualityTest eq, CastExpr cast | + cast.getExpr() = read and + eq.hasOperands(cast, read.getVariable().getAnAccess()) + ) +} + +class SmallType extends Type { + SmallType() { + this instanceof BooleanType or + this.(PrimitiveType).hasName("byte") or + this.(BoxedType).getPrimitiveType().hasName("byte") + } +} + +predicate smallExpr(Expr e) { + exists(int low, int high | + bounded(e, any(ZeroBound zb), low, false, _) and + bounded(e, any(ZeroBound zb), high, true, _) and + high - low < 256 + ) +} diff --git a/java/ql/src/Security/CWE/CWE-681/NumericCastTainted.java b/java/ql/src/Security/CWE/CWE-681/NumericCastTainted.java new file mode 100644 index 00000000000..6d864b25f6d --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-681/NumericCastTainted.java @@ -0,0 +1,29 @@ +class Test { + public static void main(String[] args) throws IOException { + { + long data; + + BufferedReader readerBuffered = new BufferedReader( + new InputStreamReader(System.in, "UTF-8")); + String stringNumber = readerBuffered.readLine(); + if (stringNumber != null) { + data = Long.parseLong(stringNumber.trim()); + } else { + data = 0; + } + + // AVOID: potential truncation if input data is very large, + // for example 'Long.MAX_VALUE' + int scaled = (int)data; + + //... + + // GOOD: use a guard to ensure no truncation occurs + int scaled2; + if (data > Integer.MIN_VALUE && data < Integer.MAX_VALUE) + scaled2 = (int)data; + else + throw new IllegalArgumentException("Invalid input"); + } + } +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-681/NumericCastTainted.qhelp b/java/ql/src/Security/CWE/CWE-681/NumericCastTainted.qhelp new file mode 100644 index 00000000000..6dd83fab76d --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-681/NumericCastTainted.qhelp @@ -0,0 +1,50 @@ + + + +

    Casting a user-controlled numeric value to a narrower type can result in truncated values +unless the input is validated.

    + +

    Narrowing conversions may cause potentially unintended results. +For example, casting the positive integer value 128 to type byte +yields the negative value -128.

    + +
    + + +

    Guard against unexpected truncation of user-controlled arithmetic data by doing one of the +following:

    + +
      +
    • Validate the user input.
    • +
    • Define a guard on the cast expression, so that the cast is performed only if the +input is known to be within the range of the resulting type.
    • +
    • Avoid casting to a narrower type, and instead continue to use a wider type.
    • +
    + +
    + + +

    In this example, a value is read from standard input into a long. Because the value +is a user-controlled value, it could be extremely large. Casting this value to a narrower type +could therefore cause unexpected truncation. The scaled2 example uses a guard to avoid +this problem and checks the range of the input before performing the cast. If the value is too large +to cast to type int it is rejected as invalid. +

    + + + +
    + + +
  • The CERT Oracle Secure Coding Standard for Java: + NUM12-J. Ensure conversions of numeric types to narrower types do not result in lost or misinterpreted data.
  • + + + + + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-681/NumericCastTainted.ql b/java/ql/src/Security/CWE/CWE-681/NumericCastTainted.ql new file mode 100644 index 00000000000..1f363dccba3 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-681/NumericCastTainted.ql @@ -0,0 +1,36 @@ +/** + * @name User-controlled data in numeric cast + * @description Casting user-controlled numeric data to a narrower type without validation + * can cause unexpected truncation. + * @kind problem + * @problem.severity error + * @precision high + * @id java/tainted-numeric-cast + * @tags security + * external/cwe/cwe-197 + * external/cwe/cwe-681 + */ +import java +import semmle.code.java.dataflow.FlowSources +import NumericCastCommon + +private class NumericCastFlowConfig extends TaintTracking::Configuration { + NumericCastFlowConfig() { this = "NumericCastTainted::RemoteUserInputToNumericNarrowingCastExpr" } + override predicate isSource(DataFlow::Node src) { src instanceof RemoteUserInput } + override predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(NumericNarrowingCastExpr cast).getExpr() } + override predicate isSanitizer(DataFlow::Node node) { + boundedRead(node.asExpr()) or + castCheck(node.asExpr()) or + node.getType() instanceof SmallType or + smallExpr(node.asExpr()) or + node.getEnclosingCallable() instanceof HashCodeMethod + } +} + +from NumericNarrowingCastExpr exp, VarAccess tainted, RemoteUserInput origin, NumericCastFlowConfig conf +where + exp.getExpr() = tainted and + conf.hasFlow(origin, DataFlow::exprNode(tainted)) and + not exists(RightShiftOp e | e.getShiftedVariable() = tainted.getVariable()) +select exp, "$@ flows to here and is cast to a narrower type, potentially causing truncation.", + origin, "User-provided value" diff --git a/java/ql/src/Security/CWE/CWE-681/NumericCastTaintedLocal.qhelp b/java/ql/src/Security/CWE/CWE-681/NumericCastTaintedLocal.qhelp new file mode 100644 index 00000000000..d225599aca1 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-681/NumericCastTaintedLocal.qhelp @@ -0,0 +1,5 @@ + + + diff --git a/java/ql/src/Security/CWE/CWE-681/NumericCastTaintedLocal.ql b/java/ql/src/Security/CWE/CWE-681/NumericCastTaintedLocal.ql new file mode 100644 index 00000000000..b9e2eff2b3a --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-681/NumericCastTaintedLocal.ql @@ -0,0 +1,36 @@ +/** + * @name Local-user-controlled data in numeric cast + * @description Casting user-controlled numeric data to a narrower type without validation + * can cause unexpected truncation. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/tainted-numeric-cast-local + * @tags security + * external/cwe/cwe-197 + * external/cwe/cwe-681 + */ +import java +import semmle.code.java.dataflow.FlowSources +import NumericCastCommon + +private class NumericCastFlowConfig extends TaintTracking::Configuration { + NumericCastFlowConfig() { this = "NumericCastTaintedLocal::LocalUserInputToNumericNarrowingCastExpr" } + override predicate isSource(DataFlow::Node src) { src instanceof LocalUserInput } + override predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(NumericNarrowingCastExpr cast).getExpr() } + override predicate isSanitizer(DataFlow::Node node) { + boundedRead(node.asExpr()) or + castCheck(node.asExpr()) or + node.getType() instanceof SmallType or + smallExpr(node.asExpr()) or + node.getEnclosingCallable() instanceof HashCodeMethod + } +} + +from NumericNarrowingCastExpr exp, VarAccess tainted, LocalUserInput origin, NumericCastFlowConfig conf +where + exp.getExpr() = tainted and + conf.hasFlow(origin, DataFlow::exprNode(tainted)) and + not exists(RightShiftOp e | e.getShiftedVariable() = tainted.getVariable()) +select exp, "$@ flows to here and is cast to a narrower type, potentially causing truncation.", + origin, "User-provided value" diff --git a/java/ql/src/Security/CWE/CWE-732/ReadingFromWorldWritableFile.qhelp b/java/ql/src/Security/CWE/CWE-732/ReadingFromWorldWritableFile.qhelp new file mode 100644 index 00000000000..909acdfe889 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-732/ReadingFromWorldWritableFile.qhelp @@ -0,0 +1,53 @@ + + + +

    Reading from a world-writable file is dangerous on a multi-user system because other users may be +able to affect program execution by modifying or deleting the file.

    + +
    + + +

    Do not make files explicitly world writable unless the file is intended to be written by multiple +users on a multi-user system. In many cases, the file may only need to be writable for the current +user.

    + +

    For some file systems, there may be alternatives to setting the file to be world writable. For +example, POSIX file systems support "groups" which may be used to ensure that only subset of all the +users can write to the file. Access Control Lists (ACLs) are available for many operating system +and file system combinations, and can provide fine-grained read and write support without resorting +to world writable permissions.

    + +
    + + +

    +In the following example, we are loading some configuration parameters from a file: +

    + + +private void readConfig(File configFile) { + if (!configFile.exists()) { + // Create an empty config file + configFile.createNewFile(); + // Make the file writable for all + configFile.setWritable(true, false); + } + // Now read the config + loadConfig(configFile); +} + + +

    If the configuration file does not yet exist, an empty file is created. Creating an empty file +can simplify the later code and is a convenience for the user. However, by setting the file to be +world writable, we allow any user on the system to modify the configuration, not just the current +user. If there may be untrusted users on the system, this is potentially dangerous.

    + +
    + + +
  • The CERT Oracle Secure Coding Standard for Java: + FIO01-J. Create files with appropriate access permissions.
  • +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-732/ReadingFromWorldWritableFile.ql b/java/ql/src/Security/CWE/CWE-732/ReadingFromWorldWritableFile.ql new file mode 100644 index 00000000000..ad8dbb764da --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-732/ReadingFromWorldWritableFile.ql @@ -0,0 +1,25 @@ +/** + * @name Reading from a world writable file + * @description Reading from a file which is set as world writable is dangerous because + * the file may be modified or removed by external actors. + * @kind problem + * @problem.severity error + * @precision high + * @id java/world-writable-file-read + * @tags security + * external/cwe/cwe-732 + */ + +import java +import semmle.code.java.security.FileReadWrite +import semmle.code.java.security.FileWritable + +from Variable fileVariable, FileReadExpr readFrom, SetFileWorldWritable setWorldWritable +where + // The file variable must be both read from and set to world writable. This is not flow-sensitive. + fileVariable.getAnAccess() = readFrom.getFileVarAccess() and + fileVariable.getAnAccess() = setWorldWritable.getFileVarAccess() and + // If the file variable is a parameter, the result should be reported in the caller. + not fileVariable instanceof Parameter +select setWorldWritable, "A file is set to be world writable here, but is read from $@.", + readFrom, "statement" diff --git a/java/ql/src/Security/CWE/CWE-798/HardcodedCredentials.qll b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentials.qll new file mode 100644 index 00000000000..09024f43132 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentials.qll @@ -0,0 +1,104 @@ +import java +import SensitiveApi + +/** + * An array creation expression of type `byte[]` with + * an initializer containing only compile time constant + * expressions (and at least one such expression). + */ +private class HardcodedByteArray extends ArrayCreationExpr { + HardcodedByteArray() { + getType().(Array).getElementType().(PrimitiveType).getName() = "byte" and + forex(Expr elem | elem = getInit().getAChildExpr() | + elem instanceof CompileTimeConstantExpr + ) + } +} + +/** + * An array creation expression of type `char[]` with + * an initializer containing only compile time constant + * expressions (and at least one such expression). + */ +private class HardcodedCharArray extends ArrayCreationExpr { + HardcodedCharArray() { + getType().(Array).getElementType().(PrimitiveType).getName() = "char" and + forex(Expr elem | elem = getInit().getAChildExpr() | + elem instanceof CompileTimeConstantExpr + ) + } +} + +/** + * An expression that is either a non-empty string literal or a + * hard-coded `byte` or `char` array. + */ +class HardcodedExpr extends Expr { + HardcodedExpr() { + this.(StringLiteral).getRepresentedString() != "" or + this instanceof HardcodedByteArray or + this instanceof HardcodedCharArray + } +} + +/** + * An argument to a sensitive call, expected to contain credentials. + */ +abstract class CredentialsSink extends Expr { + Call getSurroundingCall() { + this = result.getAnArgument() + } +} + +/** + * An argument to a sensitive call of a known API, + * expected to contain username, password or cryptographic key + * credentials. + */ +class CredentialsApiSink extends CredentialsSink { + CredentialsApiSink() { + exists(Call call, int i | + this = call.getArgument(i) and + ( + javaApiCallableUsernameParam(call.getCallee(), i) or + javaApiCallablePasswordParam(call.getCallee(), i) or + javaApiCallableCryptoKeyParam(call.getCallee(), i) or + otherApiCallableCredentialParam(call.getCallee(), i) + ) + ) + } +} + +/** + * A variable whose name indicates that it may hold a password. + */ +class PasswordVariable extends Variable { + PasswordVariable() { + getName().regexpMatch("(?i)(encrypted|old|new)?pass(wd|word|code|phrase)(chars|value)?") + } +} + +/** + * A variable whose name indicates that it may hold a user name. + */ +class UsernameVariable extends Variable { + UsernameVariable() { + getName().regexpMatch("(?i)(user|username)") + } +} + +/** + * An argument to a call, where the parameter name corresponding + * to the argument indicates that it may contain credentials. + */ +class CredentialsSourceSink extends CredentialsSink { + CredentialsSourceSink() { + exists(Call call, int i | + this = call.getArgument(i) and + ( + call.getCallee().getParameter(i) instanceof UsernameVariable or + call.getCallee().getParameter(i) instanceof PasswordVariable + ) + ) + } +} diff --git a/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.java b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.java new file mode 100644 index 00000000000..a12a9881d1a --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.java @@ -0,0 +1,12 @@ +private static final String p = "123456"; // hard-coded credential + +public static void main(String[] args) throws SQLException { + String url = "jdbc:mysql://localhost/test"; + String u = "admin"; // hard-coded credential + + getConn(url, u, p); +} + +public static void getConn(String url, String v, String q) throws SQLException { + DriverManager.getConnection(url, v, q); // sensitive call +} diff --git a/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.qhelp b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.qhelp new file mode 100644 index 00000000000..231140a287e --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.qhelp @@ -0,0 +1,44 @@ + + + + +

    + Including unencrypted hard-coded authentication credentials in source code is dangerous because + the credentials may be easily discovered. For example, the code may be open source, or it may + be leaked or accidentally revealed, making the credentials visible to an attacker. This, in turn, + might enable them to gain unauthorized access, or to obtain privileged information. +

    +
    + + +

    + Remove hard-coded credentials, such as user names, passwords and certificates, from source code. + Instead, place them in configuration files, environment variables or other data stores if necessary. + If possible, store configuration files including credential data separately from the source code, + in a secure location with restricted access. +

    +
    + + +

    + The following code example connects to a database using a hard-coded user name and password: +

    + + + +

    + Instead, the user name and password could be supplied through environment variables, + which can be set externally without hard-coding credentials in the source code. +

    +
    + + +
  • +OWASP: +Use of hard-coded password. +
  • +
    + +
    diff --git a/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.ql b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.ql new file mode 100644 index 00000000000..5fd7a87725f --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.ql @@ -0,0 +1,36 @@ +/** + * @name Hard-coded credential in API call + * @description Using a hard-coded credential in a call to a sensitive Java API may compromise security. + * @kind problem + * @problem.severity error + * @precision medium + * @id java/hardcoded-credential-api-call + * @tags security + * external/cwe/cwe-798 + */ +import java +import semmle.code.java.dataflow.DataFlow +import HardcodedCredentials + +class HardcodedCredentialApiCallConfiguration extends DataFlow::Configuration { + HardcodedCredentialApiCallConfiguration() { this = "HardcodedCredentialApiCallConfiguration" } + override predicate isSource(DataFlow::Node n) { + n.asExpr() instanceof HardcodedExpr and + not n.asExpr().getEnclosingCallable().getName() = "toString" + } + override predicate isSink(DataFlow::Node n) { + n.asExpr() instanceof CredentialsApiSink + } + override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + node1.asExpr().getType() instanceof TypeString and + exists(MethodAccess ma | ma.getMethod().getName().regexpMatch("getBytes|toCharArray") | + node2.asExpr() = ma and + ma.getQualifier() = node1.asExpr() + ) + } +} + +from CredentialsApiSink sink, HardcodedExpr source, HardcodedCredentialApiCallConfiguration conf +where conf.hasFlow(DataFlow::exprNode(source), DataFlow::exprNode(sink)) +select source, "Hard-coded value flows to $@.", + sink, "sensitive API call" diff --git a/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsComparison.qhelp b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsComparison.qhelp new file mode 100644 index 00000000000..5d7bde3be4c --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsComparison.qhelp @@ -0,0 +1,6 @@ + + + + diff --git a/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsComparison.ql b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsComparison.ql new file mode 100644 index 00000000000..eb700903cbe --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsComparison.ql @@ -0,0 +1,29 @@ +/** + * @name Hard-coded credential comparison + * @description Comparing a parameter to a hard-coded credential may compromise security. + * @kind problem + * @problem.severity error + * @precision low + * @id java/hardcoded-credential-comparison + * @tags security + * external/cwe/cwe-798 + */ +import java +import HardcodedCredentials + +class EqualsAccess extends MethodAccess { + EqualsAccess() { + getMethod() instanceof EqualsMethod + } +} + +from EqualsAccess sink, HardcodedExpr source, PasswordVariable p +where + source = sink.getQualifier() and + p.getAnAccess() = sink.getArgument(0) + or + source = sink.getArgument(0) and + p.getAnAccess() = sink.getQualifier() +select source, "Hard-coded value is $@ with password variable $@.", + sink, "compared", + p, p.getName() diff --git a/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsSourceCall.qhelp b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsSourceCall.qhelp new file mode 100644 index 00000000000..5d7bde3be4c --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsSourceCall.qhelp @@ -0,0 +1,6 @@ + + + + diff --git a/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsSourceCall.ql b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsSourceCall.ql new file mode 100644 index 00000000000..34697b112d0 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsSourceCall.ql @@ -0,0 +1,47 @@ +/** + * @name Hard-coded credential in sensitive call + * @description Using a hard-coded credential in a sensitive call may compromise security. + * @kind problem + * @problem.severity error + * @precision low + * @id java/hardcoded-credential-sensitive-call + * @tags security + * external/cwe/cwe-798 + */ +import java +import semmle.code.java.dataflow.DataFlow +import semmle.code.java.dataflow.DataFlow2 +import HardcodedCredentials + +class HardcodedCredentialSourceCallConfiguration extends DataFlow::Configuration { + HardcodedCredentialSourceCallConfiguration() { this = "HardcodedCredentialSourceCallConfiguration" } + override predicate isSource(DataFlow::Node n) { + n.asExpr() instanceof HardcodedExpr + } + override predicate isSink(DataFlow::Node n) { + n.asExpr() instanceof FinalCredentialsSourceSink + } +} + +class HardcodedCredentialSourceCallConfiguration2 extends DataFlow2::Configuration { + HardcodedCredentialSourceCallConfiguration2() { this = "HardcodedCredentialSourceCallConfiguration2" } + override predicate isSource(DataFlow::Node n) { + n.asExpr() instanceof CredentialsSourceSink + } + override predicate isSink(DataFlow::Node n) { + n.asExpr() instanceof CredentialsSink + } +} + +class FinalCredentialsSourceSink extends CredentialsSourceSink { + FinalCredentialsSourceSink() { + not exists(HardcodedCredentialSourceCallConfiguration2 conf, CredentialsSink other | this != other | + conf.hasFlow(DataFlow::exprNode(this), DataFlow::exprNode(other)) + ) + } +} + +from FinalCredentialsSourceSink sink, HardcodedExpr source, HardcodedCredentialSourceCallConfiguration conf +where conf.hasFlow(DataFlow::exprNode(source), DataFlow::exprNode(sink)) +select source, "Hard-coded value flows to $@.", + sink, "sensitive call" diff --git a/java/ql/src/Security/CWE/CWE-798/HardcodedPasswordField.qhelp b/java/ql/src/Security/CWE/CWE-798/HardcodedPasswordField.qhelp new file mode 100644 index 00000000000..5d7bde3be4c --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-798/HardcodedPasswordField.qhelp @@ -0,0 +1,6 @@ + + + + diff --git a/java/ql/src/Security/CWE/CWE-798/HardcodedPasswordField.ql b/java/ql/src/Security/CWE/CWE-798/HardcodedPasswordField.ql new file mode 100644 index 00000000000..dc78ba7ee6b --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-798/HardcodedPasswordField.ql @@ -0,0 +1,20 @@ +/** + * @name Hard-coded password field + * @description Hard-coding a password string may compromise security. + * @kind problem + * @problem.severity error + * @precision low + * @id java/hardcoded-password-field + * @tags security + * external/cwe/cwe-798 + */ +import java +import HardcodedCredentials + +from PasswordVariable f, CompileTimeConstantExpr e +where + f instanceof Field and + f.getAnAssignedValue() = e and + not e.(StringLiteral).getValue() = "" +select f, "Sensitive field is assigned a hard-coded $@.", + e, "value" diff --git a/java/ql/src/Security/CWE/CWE-798/SensitiveApi.qll b/java/ql/src/Security/CWE/CWE-798/SensitiveApi.qll new file mode 100644 index 00000000000..eb87d244a68 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-798/SensitiveApi.qll @@ -0,0 +1,424 @@ +import java + +/** + * Holds if callable `c` from a standard Java API expects a password parameter at index `i`. + */ +predicate javaApiCallablePasswordParam(Callable c, int i) { + exists(c.getParameter(i)) and + javaApiCallablePasswordParam(c.getDeclaringType().getQualifiedName() + ";" + c.getStringSignature() + ";" + i) +} + +private predicate javaApiCallablePasswordParam(string s) { + // Auto-generated using an auxiliary query run on the JDK source code. + s = "com.sun.crypto.provider.JceKeyStore;engineLoad(InputStream, char[]);1" or + s = "com.sun.crypto.provider.JceKeyStore;engineGetKey(String, char[]);1" or + s = "com.sun.crypto.provider.JceKeyStore;engineSetKeyEntry(String, Key, char[], Certificate[]);2" or + s = "com.sun.crypto.provider.JceKeyStore;engineStore(OutputStream, char[]);1" or + s = "com.sun.crypto.provider.JceKeyStore;getPreKeyedHash(char[]);0" or + s = "com.sun.crypto.provider.KeyProtector;KeyProtector(char[]);0" or + s = "com.sun.crypto.provider.PBKDF2KeyImpl;deriveKey(Mac, byte[], byte[], int, int);1" or + s = "com.sun.crypto.provider.PBKDF2KeyImpl;getPasswordBytes(char[]);0" or + s = "com.sun.istack.internal.tools.DefaultAuthenticator$AuthInfo;AuthInfo(URL, String, String);2" or + s = "com.sun.net.httpserver.BasicAuthenticator;checkCredentials(String, String);1" or + s = "com.sun.net.ssl.KeyManagerFactory;init(KeyStore, char[]);1" or + s = "com.sun.net.ssl.KeyManagerFactorySpi;engineInit(KeyStore, char[]);1" or + s = "com.sun.net.ssl.KeyManagerFactorySpiWrapper;engineInit(KeyStore, char[]);1" or + s = "com.sun.org.apache.xml.internal.security.keys.keyresolver.implementations.PrivateKeyResolver;PrivateKeyResolver(KeyStore, char[]);1" or + s = "com.sun.org.apache.xml.internal.security.keys.keyresolver.implementations.SecretKeyResolver;SecretKeyResolver(KeyStore, char[]);1" or + s = "com.sun.rowset.JdbcRowSetImpl;JdbcRowSetImpl(String, String, String);2" or + s = "com.sun.rowset.JdbcRowSetImpl;setPassword(String);0" or + s = "com.sun.security.auth.module.JndiLoginModule;verifyPassword(String, String);1" or + s = "com.sun.security.auth.module.JndiLoginModule;verifyPassword(String, String);0" or + s = "com.sun.security.ntlm.Client;Client(String, String, String, String, char[]);4" or + s = "com.sun.security.ntlm.NTLM;getP2(char[]);0" or + s = "com.sun.security.ntlm.NTLM;getP1(char[]);0" or + s = "com.sun.security.sasl.digest.DigestMD5Base;generateResponseValue(String, String, String, String, String, char[], byte[], byte[], int, byte[]);5" or + s = "com.sun.security.sasl.digest.DigestMD5Server;generateResponseAuth(String, char[], byte[], int, byte[]);1" or + s = "com.sun.tools.internal.ws.wscompile.AuthInfo;AuthInfo(URL, String, String);2" or + s = "java.net.PasswordAuthentication;PasswordAuthentication(String, char[]);1" or + s = "java.security.KeyStore;setKeyEntry(String, Key, char[], Certificate[]);2" or + s = "java.security.KeyStore;store(OutputStream, char[]);1" or + s = "java.security.KeyStore;getKey(String, char[]);1" or + s = "java.security.KeyStore;load(InputStream, char[]);1" or + s = "java.security.KeyStore$PasswordProtection;PasswordProtection(char[], String, AlgorithmParameterSpec);0" or + s = "java.security.KeyStore$PasswordProtection;PasswordProtection(char[]);0" or + s = "java.security.KeyStoreSpi;engineStore(OutputStream, char[]);1" or + s = "java.security.KeyStoreSpi;engineLoad(InputStream, char[]);1" or + s = "java.security.KeyStoreSpi;engineSetKeyEntry(String, Key, char[], Certificate[]);2" or + s = "java.security.KeyStoreSpi;engineGetKey(String, char[]);1" or + s = "java.sql.DriverManager;getConnection(String, String, String);2" or + s = "javax.crypto.spec.PBEKeySpec;PBEKeySpec(char[], byte[], int);0" or + s = "javax.crypto.spec.PBEKeySpec;PBEKeySpec(char[], byte[], int, int);0" or + s = "javax.crypto.spec.PBEKeySpec;PBEKeySpec(char[]);0" or + s = "javax.net.ssl.KeyManagerFactory;init(KeyStore, char[]);1" or + s = "javax.net.ssl.KeyManagerFactorySpi;engineInit(KeyStore, char[]);1" or + s = "javax.security.auth.callback.PasswordCallback;setPassword(char[]);0" or + s = "javax.security.auth.kerberos.KerberosKey;KerberosKey(KerberosPrincipal, char[], String);1" or + s = "javax.security.auth.kerberos.KeyImpl;KeyImpl(KerberosPrincipal, char[], String);1" or + s = "javax.sql.ConnectionPoolDataSource;getPooledConnection(String, String);1" or + s = "javax.sql.DataSource;getConnection(String, String);1" or + s = "javax.sql.RowSet;setPassword(String);0" or + s = "javax.sql.XADataSource;getXAConnection(String, String);1" or + s = "sun.net.ftp.FtpClient;login(String, char[]);1" or + s = "sun.net.ftp.FtpClient;login(String, char[], String);1" or + s = "sun.net.ftp.impl.FtpClient;login(String, char[], String);1" or + s = "sun.net.ftp.impl.FtpClient;login(String, char[]);1" or + s = "sun.net.ftp.impl.FtpClient;tryLogin(String, char[]);1" or + s = "sun.net.www.protocol.http.DigestAuthentication;encode(String, char[], MessageDigest);1" or + s = "sun.net.www.protocol.http.DigestAuthentication;computeDigest(boolean, String, char[], String, String, String, String, String, String);2" or + s = "sun.security.krb5.EncryptionKey;acquireSecretKey(char[], String, int, byte[]);0" or + s = "sun.security.krb5.EncryptionKey;stringToKey(char[], String, byte[], int);0" or + s = "sun.security.krb5.EncryptionKey;EncryptionKey(char[], String, String);0" or + s = "sun.security.krb5.EncryptionKey;acquireSecretKeys(char[], String);0" or + s = "sun.security.krb5.EncryptionKey;acquireSecretKey(PrincipalName, char[], int, SaltAndParams);1" or + s = "sun.security.krb5.KrbAsRep;decryptUsingPassword(char[], KrbAsReq, PrincipalName);0" or + s = "sun.security.krb5.internal.crypto.Aes128;stringToKey(char[], String, byte[]);0" or + s = "sun.security.krb5.internal.crypto.Aes256;stringToKey(char[], String, byte[]);0" or + s = "sun.security.krb5.internal.crypto.ArcFourHmac;stringToKey(char[]);0" or + s = "sun.security.krb5.internal.crypto.Des;char_to_key(char[]);0" or + s = "sun.security.krb5.internal.crypto.Des;string_to_key_bytes(char[]);0" or + s = "sun.security.krb5.internal.crypto.dk.AesDkCrypto;stringToKey(char[], String, byte[]);0" or + s = "sun.security.krb5.internal.crypto.dk.ArcFourCrypto;stringToKey(char[]);0" or + s = "sun.security.pkcs11.P11KeyStore;engineLoad(InputStream, char[]);1" or + s = "sun.security.pkcs11.P11KeyStore;engineGetKey(String, char[]);1" or + s = "sun.security.pkcs11.P11KeyStore;engineStore(OutputStream, char[]);1" or + s = "sun.security.pkcs11.P11KeyStore;engineSetKeyEntry(String, Key, char[], Certificate[]);2" or + s = "sun.security.pkcs11.P11KeyStore$PasswordCallbackHandler;PasswordCallbackHandler(char[]);0" or + s = "sun.security.pkcs11.Secmod$KeyStoreLoadParameter;KeyStoreLoadParameter(TrustType, char[]);1" or + s = "sun.security.pkcs12.PKCS12KeyStore;engineGetKey(String, char[]);1" or + s = "sun.security.pkcs12.PKCS12KeyStore;calculateMac(char[], byte[]);0" or + s = "sun.security.pkcs12.PKCS12KeyStore;encryptContent(byte[], char[]);1" or + s = "sun.security.pkcs12.PKCS12KeyStore;loadSafeContents(DerInputStream, char[]);1" or + s = "sun.security.pkcs12.PKCS12KeyStore;engineSetKeyEntry(String, Key, char[], Certificate[]);2" or + s = "sun.security.pkcs12.PKCS12KeyStore;engineStore(OutputStream, char[]);1" or + s = "sun.security.pkcs12.PKCS12KeyStore;engineLoad(InputStream, char[]);1" or + s = "sun.security.pkcs12.PKCS12KeyStore;getPBEKey(char[]);0" or + s = "sun.security.pkcs12.PKCS12KeyStore;createEncryptedData(char[]);0" or + s = "sun.security.provider.DomainKeyStore;engineGetKey(String, char[]);1" or + s = "sun.security.provider.DomainKeyStore;engineSetKeyEntry(String, Key, char[], Certificate[]);2" or + s = "sun.security.provider.DomainKeyStore;engineStore(OutputStream, char[]);1" or + s = "sun.security.provider.DomainKeyStore;engineLoad(InputStream, char[]);1" or + s = "sun.security.provider.JavaKeyStore;engineSetKeyEntry(String, Key, char[], Certificate[]);2" or + s = "sun.security.provider.JavaKeyStore;engineLoad(InputStream, char[]);1" or + s = "sun.security.provider.JavaKeyStore;getPreKeyedHash(char[]);0" or + s = "sun.security.provider.JavaKeyStore;engineGetKey(String, char[]);1" or + s = "sun.security.provider.JavaKeyStore;engineStore(OutputStream, char[]);1" or + s = "sun.security.provider.KeyProtector;KeyProtector(char[]);0" or + s = "sun.security.ssl.KeyManagerFactoryImpl$SunX509;engineInit(KeyStore, char[]);1" or + s = "sun.security.ssl.KeyManagerFactoryImpl$X509;engineInit(KeyStore, char[]);1" or + s = "sun.security.ssl.SunX509KeyManagerImpl;SunX509KeyManagerImpl(KeyStore, char[]);1" or + s = "sun.security.tools.keytool.Main;getNewPasswd(String, char[]);1" or + s = "sun.tools.jconsole.ConnectDialog;setConnectionParameters(String, String, int, String, String, String);4" or + s = "sun.tools.jconsole.JConsole;addHost(String, int, String, String);3" or + s = "sun.tools.jconsole.JConsole;addUrl(String, String, String, boolean);2" or + s = "sun.tools.jconsole.JConsole;addHost(String, int, String, String, boolean);3" or + s = "sun.tools.jconsole.JConsole;showConnectDialog(String, String, int, String, String, String);4" or + s = "sun.tools.jconsole.JConsole;failed(Exception, String, String, String);3" or + s = "sun.tools.jconsole.ProxyClient;getCacheKey(String, String, String);2" or + s = "sun.tools.jconsole.ProxyClient;setParameters(JMXServiceURL, String, String);2" or + s = "sun.tools.jconsole.ProxyClient;ProxyClient(String, String, String);2" or + s = "sun.tools.jconsole.ProxyClient;ProxyClient(String, int, String, String);3" or + s = "sun.tools.jconsole.ProxyClient;getProxyClient(String, int, String, String);3" or + s = "sun.tools.jconsole.ProxyClient;getProxyClient(String, String, String);2" or + s = "sun.tools.jconsole.ProxyClient;getCacheKey(String, int, String, String);3" +} + +/** + * Holds if callable `c` from a standard Java API expects a username parameter at index `i`. + */ +predicate javaApiCallableUsernameParam(Callable c, int i) { + exists(c.getParameter(i)) and + javaApiCallableUsernameParam(c.getDeclaringType().getQualifiedName() + ";" + c.getStringSignature() + ";" + i) +} + +private predicate javaApiCallableUsernameParam(string s) { + // Auto-generated using an auxiliary query run on the JDK source code. + s = "com.sun.istack.internal.tools.DefaultAuthenticator$AuthInfo;AuthInfo(URL, String, String);1" or + s = "com.sun.jndi.ldap.DigestClientId;DigestClientId(int, String, int, String, Control[], OutputStream, String, String, Object, Hashtable);7" or + s = "com.sun.jndi.ldap.LdapClient;getInstance(boolean, String, int, String, int, int, OutputStream, int, String, Control[], String, String, Object, Hashtable);11" or + s = "com.sun.jndi.ldap.LdapPoolManager;getLdapClient(String, int, String, int, int, OutputStream, int, String, Control[], String, String, Object, Hashtable);10" or + s = "com.sun.jndi.ldap.SimpleClientId;SimpleClientId(int, String, int, String, Control[], OutputStream, String, String, Object);7" or + s = "com.sun.net.httpserver.BasicAuthenticator;checkCredentials(String, String);0" or + s = "com.sun.net.httpserver.HttpPrincipal;HttpPrincipal(String, String);0" or + s = "com.sun.rowset.JdbcRowSetImpl;JdbcRowSetImpl(String, String, String);1" or + s = "com.sun.security.ntlm.Client;Client(String, String, String, String, char[]);2" or + s = "com.sun.security.ntlm.Server;getPassword(String, String);1" or + s = "com.sun.security.sasl.digest.DigestMD5Server;generateResponseAuth(String, char[], byte[], int, byte[]);0" or + s = "com.sun.tools.internal.ws.wscompile.AuthInfo;AuthInfo(URL, String, String);1" or + s = "java.net.PasswordAuthentication;PasswordAuthentication(String, char[]);0" or + s = "java.sql.DriverManager;getConnection(String, String, String);1" or + s = "javax.print.attribute.standard.JobOriginatingUserName;JobOriginatingUserName(String, Locale);0" or + s = "javax.print.attribute.standard.RequestingUserName;RequestingUserName(String, Locale);0" or + s = "javax.sql.ConnectionPoolDataSource;getPooledConnection(String, String);0" or + s = "javax.sql.DataSource;getConnection(String, String);0" or + s = "javax.sql.XADataSource;getXAConnection(String, String);0" or + s = "sun.jvmstat.perfdata.monitor.protocol.local.LocalVmManager;LocalVmManager(String);0" or + s = "sun.jvmstat.perfdata.monitor.protocol.local.PerfDataFile;getFile(String, int);0" or + s = "sun.jvmstat.perfdata.monitor.protocol.local.PerfDataFile;getTempDirectory(String);0" or + s = "sun.jvmstat.perfdata.monitor.protocol.rmi.RemoteVmManager;RemoteVmManager(RemoteHost, String);1" or + s = "sun.misc.Perf;attach(String, int, int);0" or + s = "sun.misc.Perf;attach(String, int, String);0" or + s = "sun.misc.Perf;attachImpl(String, int, int);0" or + s = "sun.net.ftp.FtpClient;login(String, char[], String);0" or + s = "sun.net.ftp.FtpClient;login(String, char[]);0" or + s = "sun.net.ftp.FtpDirEntry;setUser(String);0" or + s = "sun.net.ftp.impl.FtpClient;login(String, char[], String);0" or + s = "sun.net.ftp.impl.FtpClient;tryLogin(String, char[]);0" or + s = "sun.net.ftp.impl.FtpClient;login(String, char[]);0" or + s = "sun.net.www.protocol.http.DigestAuthentication;computeDigest(boolean, String, char[], String, String, String, String, String, String);1" or + s = "sun.security.acl.PrincipalImpl;PrincipalImpl(String);0" or + s = "sun.tools.jconsole.ConnectDialog;setConnectionParameters(String, String, int, String, String, String);3" or + s = "sun.tools.jconsole.JConsole;failed(Exception, String, String, String);2" or + s = "sun.tools.jconsole.JConsole;addHost(String, int, String, String, boolean);2" or + s = "sun.tools.jconsole.JConsole;addUrl(String, String, String, boolean);1" or + s = "sun.tools.jconsole.JConsole;addHost(String, int, String, String);2" or + s = "sun.tools.jconsole.JConsole;showConnectDialog(String, String, int, String, String, String);3" or + s = "sun.tools.jconsole.ProxyClient;ProxyClient(String, String, String);1" or + s = "sun.tools.jconsole.ProxyClient;ProxyClient(String, int, String, String);2" or + s = "sun.tools.jconsole.ProxyClient;setParameters(JMXServiceURL, String, String);1" or + s = "sun.tools.jconsole.ProxyClient;getCacheKey(String, String, String);1" or + s = "sun.tools.jconsole.ProxyClient;getCacheKey(String, int, String, String);2" or + s = "sun.tools.jconsole.ProxyClient;getProxyClient(String, String, String);1" or + s = "sun.tools.jconsole.ProxyClient;getConnectionName(String, String);1" or + s = "sun.tools.jconsole.ProxyClient;getProxyClient(String, int, String, String);2" or + s = "sun.tools.jconsole.ProxyClient;getConnectionName(String, int, String);2" +} + +/** + * Holds if callable `c` from a standard Java API expects a cryptographic key parameter at index `i`. + */ +predicate javaApiCallableCryptoKeyParam(Callable c, int i) { + exists(c.getParameter(i)) and + javaApiCallableCryptoKeyParam(c.getDeclaringType().getQualifiedName() + ";" + c.getStringSignature() + ";" + i) +} + +private predicate javaApiCallableCryptoKeyParam(string s) { + // Auto-generated using an auxiliary query run on the JDK source code. + s = "com.sun.crypto.provider.AESCipher;engineUnwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.AESCrypt;init(boolean, String, byte[]);2" or + s = "com.sun.crypto.provider.AESWrapCipher;engineUnwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.ARCFOURCipher;init(byte[]);0" or + s = "com.sun.crypto.provider.ARCFOURCipher;engineUnwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.BlowfishCipher;engineUnwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.BlowfishCrypt;init(boolean, String, byte[]);2" or + s = "com.sun.crypto.provider.CipherBlockChaining;init(boolean, String, byte[], byte[]);2" or + s = "com.sun.crypto.provider.CipherCore;unwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.CipherFeedback;init(boolean, String, byte[], byte[]);2" or + s = "com.sun.crypto.provider.CipherWithWrappingSpi;constructPublicKey(byte[], String);0" or + s = "com.sun.crypto.provider.CipherWithWrappingSpi;engineUnwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.CipherWithWrappingSpi;constructSecretKey(byte[], String);0" or + s = "com.sun.crypto.provider.CipherWithWrappingSpi;constructPrivateKey(byte[], String);0" or + s = "com.sun.crypto.provider.ConstructKeys;constructPrivateKey(byte[], String);0" or + s = "com.sun.crypto.provider.ConstructKeys;constructSecretKey(byte[], String);0" or + s = "com.sun.crypto.provider.ConstructKeys;constructPublicKey(byte[], String);0" or + s = "com.sun.crypto.provider.CounterMode;init(boolean, String, byte[], byte[]);2" or + s = "com.sun.crypto.provider.DESCipher;engineUnwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.DESCrypt;expandKey(byte[]);0" or + s = "com.sun.crypto.provider.DESCrypt;init(boolean, String, byte[]);2" or + s = "com.sun.crypto.provider.DESKey;DESKey(byte[], int);0" or + s = "com.sun.crypto.provider.DESKey;DESKey(byte[]);0" or + s = "com.sun.crypto.provider.DESKeyGenerator;setParityBit(byte[], int);0" or + s = "com.sun.crypto.provider.DESedeCipher;engineUnwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.DESedeKey;DESedeKey(byte[], int);0" or + s = "com.sun.crypto.provider.DESedeKey;DESedeKey(byte[]);0" or + s = "com.sun.crypto.provider.DESedeWrapCipher;engineUnwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.DHPrivateKey;DHPrivateKey(byte[]);0" or + s = "com.sun.crypto.provider.DHPublicKey;DHPublicKey(byte[]);0" or + s = "com.sun.crypto.provider.ElectronicCodeBook;init(boolean, String, byte[], byte[]);2" or + s = "com.sun.crypto.provider.FeedbackCipher;init(boolean, String, byte[], byte[]);2" or + s = "com.sun.crypto.provider.GaloisCounterMode;init(boolean, String, byte[], byte[]);2" or + s = "com.sun.crypto.provider.GaloisCounterMode;init(boolean, String, byte[], byte[], int);2" or + s = "com.sun.crypto.provider.JceKeyStore;engineSetKeyEntry(String, byte[], Certificate[]);1" or + s = "com.sun.crypto.provider.KeyProtector;recover(byte[]);0" or + s = "com.sun.crypto.provider.OutputFeedback;init(boolean, String, byte[], byte[]);2" or + s = "com.sun.crypto.provider.PBECipherCore;unwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.PBES1Core;unwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.PBES2Core;engineUnwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.PBEWithMD5AndDESCipher;engineUnwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.PBEWithMD5AndTripleDESCipher;engineUnwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.PCBC;init(boolean, String, byte[], byte[]);2" or + s = "com.sun.crypto.provider.PKCS12PBECipherCore;implUnwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.PKCS12PBECipherCore$PBEWithSHA1AndDESede;engineUnwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.PKCS12PBECipherCore$PBEWithSHA1AndRC2_128;engineUnwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.PKCS12PBECipherCore$PBEWithSHA1AndRC2_40;engineUnwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.PKCS12PBECipherCore$PBEWithSHA1AndRC4_128;engineUnwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.PKCS12PBECipherCore$PBEWithSHA1AndRC4_40;engineUnwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.RC2Cipher;engineUnwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.RC2Crypt;init(boolean, String, byte[]);2" or + s = "com.sun.crypto.provider.RSACipher;engineUnwrap(byte[], String, int);0" or + s = "com.sun.crypto.provider.SymmetricCipher;init(boolean, String, byte[]);2" or + s = "com.sun.crypto.provider.TlsMasterSecretGenerator$TlsMasterSecretKey;TlsMasterSecretKey(byte[], int, int);0" or + s = "java.security.KeyStore;setKeyEntry(String, byte[], Certificate[]);1" or + s = "java.security.KeyStoreSpi;engineSetKeyEntry(String, byte[], Certificate[]);1" or + s = "java.security.cert.X509CertSelector;setSubjectPublicKey(byte[]);0" or + s = "java.security.spec.EncodedKeySpec;EncodedKeySpec(byte[]);0" or + s = "java.security.spec.PKCS8EncodedKeySpec;PKCS8EncodedKeySpec(byte[]);0" or + s = "java.security.spec.X509EncodedKeySpec;X509EncodedKeySpec(byte[]);0" or + s = "javax.crypto.Cipher;unwrap(byte[], String, int);0" or + s = "javax.crypto.CipherSpi;engineUnwrap(byte[], String, int);0" or + s = "javax.crypto.EncryptedPrivateKeyInfo;checkPKCS8Encoding(byte[]);0" or + s = "javax.crypto.spec.DESKeySpec;isWeak(byte[], int);0" or + s = "javax.crypto.spec.DESKeySpec;DESKeySpec(byte[], int);0" or + s = "javax.crypto.spec.DESKeySpec;isParityAdjusted(byte[], int);0" or + s = "javax.crypto.spec.DESKeySpec;DESKeySpec(byte[]);0" or + s = "javax.crypto.spec.DESedeKeySpec;isParityAdjusted(byte[], int);0" or + s = "javax.crypto.spec.DESedeKeySpec;DESedeKeySpec(byte[], int);0" or + s = "javax.crypto.spec.DESedeKeySpec;DESedeKeySpec(byte[]);0" or + s = "javax.crypto.spec.SecretKeySpec;SecretKeySpec(byte[], String);0" or + s = "javax.crypto.spec.SecretKeySpec;SecretKeySpec(byte[], int, int, String);0" or + s = "javax.security.auth.kerberos.KerberosKey;KerberosKey(KerberosPrincipal, byte[], int, int);1" or + s = "javax.security.auth.kerberos.KerberosTicket;KerberosTicket(byte[], KerberosPrincipal, KerberosPrincipal, byte[], int, boolean[], Date, Date, Date, Date, InetAddress[]);3" or + s = "javax.security.auth.kerberos.KerberosTicket;init(byte[], KerberosPrincipal, KerberosPrincipal, byte[], int, boolean[], Date, Date, Date, Date, InetAddress[]);3" or + s = "javax.security.auth.kerberos.KeyImpl;KeyImpl(byte[], int);0" or + s = "sun.security.jgss.krb5.CipherHelper;getInitializedDes(boolean, byte[], byte[]);1" or + s = "sun.security.jgss.krb5.CipherHelper;getDesCbcChecksum(byte[], byte[], byte[], int, int);0" or + s = "sun.security.jgss.krb5.CipherHelper;getDesEncryptionKey(byte[]);0" or + s = "sun.security.jgss.krb5.CipherHelper;desCbcDecrypt(WrapToken, byte[], byte[], int, int, byte[], int);1" or + s = "sun.security.jgss.krb5.CipherHelper;desCbcDecrypt(WrapToken, byte[], InputStream, int, byte[], int);1" or + s = "sun.security.jgss.krb5.Krb5InitCredential;Krb5InitCredential(Krb5NameElement, byte[], KerberosPrincipal, KerberosPrincipal, byte[], int, boolean[], Date, Date, Date, Date, InetAddress[]);4" or + s = "sun.security.jgss.krb5.Krb5InitCredential;Krb5InitCredential(Krb5NameElement, Credentials, byte[], KerberosPrincipal, KerberosPrincipal, byte[], int, boolean[], Date, Date, Date, Date, InetAddress[]);5" or + s = "sun.security.krb5.Credentials;Credentials(byte[], String, String, byte[], int, boolean[], Date, Date, Date, Date, InetAddress[]);3" or + s = "sun.security.krb5.EncryptionKey;EncryptionKey(int, byte[]);1" or + s = "sun.security.krb5.EncryptionKey;EncryptionKey(byte[], int, Integer);0" or + s = "sun.security.krb5.internal.crypto.Aes128;decryptRaw(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.Aes128;calculateChecksum(byte[], int, byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.Aes128;decrypt(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.Aes128;encryptRaw(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.Aes128;encrypt(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.Aes128CtsHmacSha1EType;encrypt(byte[], byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.Aes128CtsHmacSha1EType;decrypt(byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.Aes128CtsHmacSha1EType;encrypt(byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.Aes128CtsHmacSha1EType;decrypt(byte[], byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.Aes256;encrypt(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.Aes256;decryptRaw(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.Aes256;calculateChecksum(byte[], int, byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.Aes256;encryptRaw(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.Aes256;decrypt(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType;encrypt(byte[], byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType;decrypt(byte[], byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType;decrypt(byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType;encrypt(byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.ArcFourHmac;encryptRaw(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.ArcFourHmac;decryptRaw(byte[], int, byte[], byte[], int, int, byte[]);0" or + s = "sun.security.krb5.internal.crypto.ArcFourHmac;decrypt(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.ArcFourHmac;decryptSeq(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.ArcFourHmac;encrypt(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.ArcFourHmac;calculateChecksum(byte[], int, byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.ArcFourHmac;encryptSeq(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.ArcFourHmacEType;decrypt(byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.ArcFourHmacEType;encrypt(byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.ArcFourHmacEType;decrypt(byte[], byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.ArcFourHmacEType;encrypt(byte[], byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.CksumType;calculateKeyedChecksum(byte[], int, byte[], int);2" or + s = "sun.security.krb5.internal.crypto.CksumType;verifyKeyedChecksum(byte[], int, byte[], byte[], int);2" or + s = "sun.security.krb5.internal.crypto.Crc32CksumType;verifyKeyedChecksum(byte[], int, byte[], byte[], int);2" or + s = "sun.security.krb5.internal.crypto.Crc32CksumType;calculateKeyedChecksum(byte[], int, byte[], int);2" or + s = "sun.security.krb5.internal.crypto.Des;cbc_encrypt(byte[], byte[], byte[], byte[], boolean);2" or + s = "sun.security.krb5.internal.crypto.Des;set_parity(byte[]);0" or + s = "sun.security.krb5.internal.crypto.Des;bad_key(byte[]);0" or + s = "sun.security.krb5.internal.crypto.Des;des_cksum(byte[], byte[], byte[]);2" or + s = "sun.security.krb5.internal.crypto.Des3;decryptRaw(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.Des3;encrypt(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.Des3;encryptRaw(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.Des3;decrypt(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.Des3;calculateChecksum(byte[], int, byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.Des3CbcHmacSha1KdEType;encrypt(byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.Des3CbcHmacSha1KdEType;encrypt(byte[], byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.Des3CbcHmacSha1KdEType;decrypt(byte[], byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.Des3CbcHmacSha1KdEType;decrypt(byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.DesCbcCrcEType;decrypt(byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.DesCbcCrcEType;encrypt(byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.DesCbcEType;encrypt(byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.DesCbcEType;decrypt(byte[], byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.DesCbcEType;encrypt(byte[], byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.DesCbcEType;decrypt(byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.DesMacCksumType;calculateKeyedChecksum(byte[], int, byte[], int);2" or + s = "sun.security.krb5.internal.crypto.DesMacCksumType;decryptKeyedChecksum(byte[], byte[]);1" or + s = "sun.security.krb5.internal.crypto.DesMacCksumType;verifyKeyedChecksum(byte[], int, byte[], byte[], int);2" or + s = "sun.security.krb5.internal.crypto.DesMacKCksumType;calculateKeyedChecksum(byte[], int, byte[], int);2" or + s = "sun.security.krb5.internal.crypto.DesMacKCksumType;verifyKeyedChecksum(byte[], int, byte[], byte[], int);2" or + s = "sun.security.krb5.internal.crypto.EType;encrypt(byte[], byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.EType;decrypt(byte[], byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.EType;decrypt(byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.EType;encrypt(byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.HmacMd5ArcFourCksumType;verifyKeyedChecksum(byte[], int, byte[], byte[], int);2" or + s = "sun.security.krb5.internal.crypto.HmacMd5ArcFourCksumType;calculateKeyedChecksum(byte[], int, byte[], int);2" or + s = "sun.security.krb5.internal.crypto.HmacSha1Aes128CksumType;verifyKeyedChecksum(byte[], int, byte[], byte[], int);2" or + s = "sun.security.krb5.internal.crypto.HmacSha1Aes128CksumType;calculateKeyedChecksum(byte[], int, byte[], int);2" or + s = "sun.security.krb5.internal.crypto.HmacSha1Aes256CksumType;verifyKeyedChecksum(byte[], int, byte[], byte[], int);2" or + s = "sun.security.krb5.internal.crypto.HmacSha1Aes256CksumType;calculateKeyedChecksum(byte[], int, byte[], int);2" or + s = "sun.security.krb5.internal.crypto.HmacSha1Des3KdCksumType;calculateKeyedChecksum(byte[], int, byte[], int);2" or + s = "sun.security.krb5.internal.crypto.HmacSha1Des3KdCksumType;verifyKeyedChecksum(byte[], int, byte[], byte[], int);2" or + s = "sun.security.krb5.internal.crypto.NullEType;decrypt(byte[], byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.NullEType;decrypt(byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.NullEType;encrypt(byte[], byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.NullEType;encrypt(byte[], byte[], int);1" or + s = "sun.security.krb5.internal.crypto.RsaMd5CksumType;verifyKeyedChecksum(byte[], int, byte[], byte[], int);2" or + s = "sun.security.krb5.internal.crypto.RsaMd5CksumType;calculateKeyedChecksum(byte[], int, byte[], int);2" or + s = "sun.security.krb5.internal.crypto.RsaMd5DesCksumType;decryptKeyedChecksum(byte[], byte[]);1" or + s = "sun.security.krb5.internal.crypto.RsaMd5DesCksumType;verifyKeyedChecksum(byte[], int, byte[], byte[], int);2" or + s = "sun.security.krb5.internal.crypto.RsaMd5DesCksumType;calculateKeyedChecksum(byte[], int, byte[], int);2" or + s = "sun.security.krb5.internal.crypto.dk.AesDkCrypto;encryptCTS(byte[], int, byte[], byte[], byte[], int, int, boolean);0" or + s = "sun.security.krb5.internal.crypto.dk.AesDkCrypto;decrypt(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.dk.AesDkCrypto;encryptRaw(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.dk.AesDkCrypto;calculateChecksum(byte[], int, byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.dk.AesDkCrypto;encrypt(byte[], int, byte[], byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.dk.AesDkCrypto;getHmac(byte[], byte[]);0" or + s = "sun.security.krb5.internal.crypto.dk.AesDkCrypto;getCipher(byte[], byte[], int);0" or + s = "sun.security.krb5.internal.crypto.dk.AesDkCrypto;decryptRaw(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.dk.AesDkCrypto;decryptCTS(byte[], int, byte[], byte[], int, int, boolean);0" or + s = "sun.security.krb5.internal.crypto.dk.ArcFourCrypto;decryptSeq(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.dk.ArcFourCrypto;decryptRaw(byte[], int, byte[], byte[], int, int, byte[]);0" or + s = "sun.security.krb5.internal.crypto.dk.ArcFourCrypto;decrypt(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.dk.ArcFourCrypto;encryptRaw(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.dk.ArcFourCrypto;getCipher(byte[], byte[], int);0" or + s = "sun.security.krb5.internal.crypto.dk.ArcFourCrypto;encryptSeq(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.dk.ArcFourCrypto;calculateChecksum(byte[], int, byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.dk.ArcFourCrypto;encrypt(byte[], int, byte[], byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.dk.ArcFourCrypto;getHmac(byte[], byte[]);0" or + s = "sun.security.krb5.internal.crypto.dk.Des3DkCrypto;keyCorrection(byte[]);0" or + s = "sun.security.krb5.internal.crypto.dk.Des3DkCrypto;getCipher(byte[], byte[], int);0" or + s = "sun.security.krb5.internal.crypto.dk.Des3DkCrypto;getHmac(byte[], byte[]);0" or + s = "sun.security.krb5.internal.crypto.dk.Des3DkCrypto;setParityBit(byte[]);0" or + s = "sun.security.krb5.internal.crypto.dk.DkCrypto;encrypt(byte[], int, byte[], byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.dk.DkCrypto;decrypt(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.dk.DkCrypto;encryptRaw(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.dk.DkCrypto;calculateChecksum(byte[], int, byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.dk.DkCrypto;decryptRaw(byte[], int, byte[], byte[], int, int);0" or + s = "sun.security.krb5.internal.crypto.dk.DkCrypto;getHmac(byte[], byte[]);0" or + s = "sun.security.krb5.internal.crypto.dk.DkCrypto;getCipher(byte[], byte[], int);0" or + s = "sun.security.krb5.internal.crypto.dk.DkCrypto;dk(byte[], byte[]);0" or + s = "sun.security.krb5.internal.crypto.dk.DkCrypto;dr(byte[], byte[]);0" or + s = "sun.security.pkcs.PKCS8Key;decode(byte[]);0" or + s = "sun.security.pkcs.PKCS8Key;PKCS8Key(AlgorithmId, byte[]);1" or + s = "sun.security.pkcs.PKCS8Key;buildPKCS8Key(AlgorithmId, byte[]);1" or + s = "sun.security.pkcs.PKCS8Key;encode(DerOutputStream, AlgorithmId, byte[]);2" or + s = "sun.security.pkcs11.ConstructKeys;constructPublicKey(byte[], String);0" or + s = "sun.security.pkcs11.ConstructKeys;constructPrivateKey(byte[], String);0" or + s = "sun.security.pkcs11.ConstructKeys;constructSecretKey(byte[], String);0" or + s = "sun.security.pkcs11.P11Cipher;engineUnwrap(byte[], String, int);0" or + s = "sun.security.pkcs11.P11KeyStore;engineSetKeyEntry(String, byte[], Certificate[]);1" or + s = "sun.security.pkcs11.P11RSACipher;engineUnwrap(byte[], String, int);0" or + s = "sun.security.pkcs11.P11SecretKeyFactory;fixDESParity(byte[], int);0" or + s = "sun.security.pkcs12.PKCS12KeyStore;engineSetKeyEntry(String, byte[], Certificate[]);1" or + s = "sun.security.provider.DomainKeyStore;engineSetKeyEntry(String, byte[], Certificate[]);1" or + s = "sun.security.provider.JavaKeyStore;engineSetKeyEntry(String, byte[], Certificate[]);1" or + s = "sun.security.tools.keytool.Main;recoverKey(String, char[], char[]);2" or + s = "sun.security.tools.keytool.Main;getKeyPasswd(String, String, char[]);2" or + s = "sun.security.x509.X509Key;decode(byte[]);0" +} + +/** + * Holds if callable `c` from a known API expects a credential parameter at index `i`. + */ +predicate otherApiCallableCredentialParam(Callable c, int i) { + exists(c.getParameter(i)) and + otherApiCallableCredentialParam(c.getDeclaringType().getQualifiedName() + ";" + c.getStringSignature() + ";" + i) +} + +private predicate otherApiCallableCredentialParam(string s) { + s = "javax.crypto.spec.IvParameterSpec;IvParameterSpec(byte[]);0" or + s = "javax.crypto.spec.IvParameterSpec;IvParameterSpec(byte[], int, int);0" or + s = "org.springframework.security.core.userdetails.User;User(String, String, boolean, boolean, boolean, boolean, Collection);0" or + s = "org.springframework.security.core.userdetails.User;User(String, String, boolean, boolean, boolean, boolean, Collection);1" +} diff --git a/java/ql/src/Security/CWE/CWE-807/ConditionalBypass.java b/java/ql/src/Security/CWE/CWE-807/ConditionalBypass.java new file mode 100644 index 00000000000..80774f73135 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-807/ConditionalBypass.java @@ -0,0 +1,22 @@ +public boolean doLogin(String user, String password) { + Cookie adminCookie = getCookies()[0]; + + // BAD: login is executed only if the value of 'adminCookie' is 'false', + // but 'adminCookie' is controlled by the user + if(adminCookie.getValue()=="false") + return login(user, password); + + return true; +} + +public boolean doLogin(String user, String password) { + Cookie adminCookie = getCookies()[0]; + + // GOOD: use server-side information based on the credentials to decide + // whether user has privileges + boolean isAdmin = queryDbForAdminStatus(user, password); + if(!isAdmin) + return login(user, password); + + return true; +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-807/ConditionalBypass.qhelp b/java/ql/src/Security/CWE/CWE-807/ConditionalBypass.qhelp new file mode 100644 index 00000000000..026508134ee --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-807/ConditionalBypass.qhelp @@ -0,0 +1,47 @@ + + + +

    Many Java constructs enable code statements to be executed conditionally, for example if +statements and for statements. If these statements contain important authentication or +login code, and the decision about whether to execute this code is based on user-controlled data, it +may be possible for an attacker to bypass security systems by preventing this code from executing.

    + +
    + + +

    Never decide whether to authenticate a user based on data that may be +controlled by that user. If necessary, ensure that the data is +validated extensively when it is input before any authentication checks are performed.

    + +

    +It is still possible to have a system that "remembers" users, thus not requiring the user to login +on every interaction. For example, personalization settings can be applied without authentication +because this is not sensitive information. However, users should be allowed to take sensitive +actions only when they have been fully authenticated. +

    + +
    + + +

    This example shows two ways of deciding whether to authenticate a user. The first way shows a +decision that is based on the value of a cookie. Cookies can be easily controlled by the user, and +so this allows a user to become authenticated without providing valid credentials. The second, more +secure way shows a decision that is based on looking up the user in a security database.

    + + + +
    + + +
  • The CERT Oracle Secure Coding Standard for Java: + SEC02-J. Do not base security checks on untrusted sources.
  • + + + + + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-807/ConditionalBypass.ql b/java/ql/src/Security/CWE/CWE-807/ConditionalBypass.ql new file mode 100644 index 00000000000..d6a4594b8a1 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-807/ConditionalBypass.ql @@ -0,0 +1,43 @@ +/** + * @name User-controlled bypass of sensitive method + * @description User-controlled bypassing of sensitive methods may allow attackers to avoid + * passing through authentication systems. + * @kind problem + * @problem.severity error + * @precision high + * @id java/user-controlled-bypass + * @tags security + * external/cwe/cwe-807 + * external/cwe/cwe-290 + */ +import java +import semmle.code.java.dataflow.FlowSources +import semmle.code.java.security.SensitiveActions +import semmle.code.java.controlflow.Dominance +import semmle.code.java.controlflow.Guards + +/** + * Calls to a sensitive method that are controlled by a condition + * on the given expression. + */ +predicate conditionControlsMethod(MethodAccess m, Expr e) { + exists(ConditionBlock cb, SensitiveExecutionMethod def, boolean cond | + cb.controls(m.getBasicBlock(), cond) and + def = m.getMethod() and + not cb.controls(def.getAReference().getBasicBlock(), cond.booleanNot()) and + e = cb.getCondition() + ) +} + +class ConditionalBypassFlowConfig extends TaintTracking::Configuration { + ConditionalBypassFlowConfig() { this = "ConditionalBypassFlowConfig" } + override predicate isSource(DataFlow::Node source) { source instanceof UserInput } + override predicate isSink(DataFlow::Node sink) { conditionControlsMethod(_, sink.asExpr()) } +} + +from UserInput u, MethodAccess m, Expr e, ConditionalBypassFlowConfig conf +where + conditionControlsMethod(m, e) and + conf.hasFlow(u, DataFlow::exprNode(e)) +select m, "Sensitive method may not be executed depending on $@, which flows from $@.", + e, "this condition", u, "user input" diff --git a/java/ql/src/Security/CWE/CWE-807/TaintedPermissionsCheck.java b/java/ql/src/Security/CWE/CWE-807/TaintedPermissionsCheck.java new file mode 100644 index 00000000000..2961ff76a23 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-807/TaintedPermissionsCheck.java @@ -0,0 +1,12 @@ +public static void main(String[] args) { + String whatDoTheyWantToDo = args[0]; + Subject subject = SecurityUtils.getSubject(); + + // BAD: permissions decision made using tainted data + if(subject.isPermitted("domain:sublevel:" + whatDoTheyWantToDo)) + doIt(); + + // GOOD: use fixed checks + if(subject.isPermitted("domain:sublevel:whatTheMethodDoes")) + doIt(); +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-807/TaintedPermissionsCheck.qhelp b/java/ql/src/Security/CWE/CWE-807/TaintedPermissionsCheck.qhelp new file mode 100644 index 00000000000..aad94ec6041 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-807/TaintedPermissionsCheck.qhelp @@ -0,0 +1,49 @@ + + + +

    Using user-controlled data in a permissions check may allow a user +to gain unauthorized access to protected functionality or data.

    + +
    + + +

    When checking whether a user is authorized for a particular activity, +do not use data that is controlled by that user in the permissions check. +If necessary, always validate the input, ideally against a fixed +list of expected values.

    + +

    Similarly, do not decide which permission to check for based on user data. +In particular, avoid using computation to decide which permissions to check for. +Use fixed permissions for particular actions, rather than generating the +permission to check for.

    + +
    + + +

    This example, using the Apache Shiro security framework, shows two ways to specify the +permissions to check. The first way uses a string, whatDoTheyWantToDo, +to specify the permissions to check. However, this string is built from user input. +This can allow an attacker to force a check against a permission that they know they +have, rather than the permission that should be checked. For example, while trying +to access the account details of another user, the attacker could force the system to check +whether they had permissions to access their own account details, which is incorrect, +and would allow them to perform the action. The second, more secure way uses a fixed check that does +not depend on data that is controlled by the user.

    + + + +
    + + +
  • The CERT Oracle Secure Coding Standard for Java: + SEC02-J. Do not base security checks on untrusted sources.
  • + + + + + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-807/TaintedPermissionsCheck.ql b/java/ql/src/Security/CWE/CWE-807/TaintedPermissionsCheck.ql new file mode 100644 index 00000000000..14156ca0aef --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-807/TaintedPermissionsCheck.ql @@ -0,0 +1,66 @@ +/** + * @name User-controlled data used in permissions check + * @description Using user-controlled data in a permissions check may result in inappropriate + * permissions being granted. + * @kind problem + * @problem.severity error + * @precision high + * @id java/tainted-permissions-check + * @tags security + * external/cwe/cwe-807 + * external/cwe/cwe-290 + */ +import java +import semmle.code.java.dataflow.FlowSources + +class TypeShiroSubject extends RefType { + TypeShiroSubject() { + this.getQualifiedName() = "org.apache.shiro.subject.Subject" + } +} + +class TypeShiroWCPermission extends RefType { + TypeShiroWCPermission() { + this.getQualifiedName() = "org.apache.shiro.authz.permission.WildcardPermission" + } +} + +abstract class PermissionsConstruction extends Top { + abstract Expr getInput(); +} + +class PermissionsCheckMethodAccess extends MethodAccess, PermissionsConstruction { + PermissionsCheckMethodAccess() { + exists(Method m | m = this.getMethod() | + m.getDeclaringType() instanceof TypeShiroSubject and + m.getName() = "isPermitted" + or + m.getName().toLowerCase().matches("%permitted%") and + m.getNumberOfParameters() = 1 + ) + } + + override Expr getInput() { + result = getArgument(0) + } +} + +class WCPermissionConstruction extends ClassInstanceExpr, PermissionsConstruction { + WCPermissionConstruction() { + this.getConstructor().getDeclaringType() instanceof TypeShiroWCPermission + } + + override Expr getInput() { + result = getArgument(0) + } +} + +class TaintedPermissionsCheckFlowConfig extends TaintTracking::Configuration { + TaintedPermissionsCheckFlowConfig() { this = "TaintedPermissionsCheckFlowConfig" } + override predicate isSource(DataFlow::Node source) { source instanceof UserInput } + override predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(PermissionsConstruction p).getInput() } +} + +from UserInput u, PermissionsConstruction p, TaintedPermissionsCheckFlowConfig conf +where conf.hasFlow(u, DataFlow::exprNode(p.getInput())) +select p, "Permissions check uses user-controlled $@.", u, "data" diff --git a/java/ql/src/Security/CWE/CWE-833/LockOrderInconsistency.java b/java/ql/src/Security/CWE/CWE-833/LockOrderInconsistency.java new file mode 100644 index 00000000000..fef42a65b5a --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-833/LockOrderInconsistency.java @@ -0,0 +1,33 @@ +class Test { + private int primaryAccountBalance; + private Object primaryLock = new Object(); + private int savingsAccountBalance; + private Object savingsLock = new Object(); + + public boolean transferToSavings(int amount) { + synchronized(primaryLock) { + synchronized(savingsLock) { + if (amount>0 && primaryAccountBalance>=amount) { + primaryAccountBalance -= amount; + savingsAccountBalance += amount; + return true; + } + } + } + return false; + } + public boolean transferToPrimary(int amount) { + // AVOID: lock order is different from "transferToSavings" + // and may result in deadlock + synchronized(savingsLock) { + synchronized(primaryLock) { + if (amount>0 && savingsAccountBalance>=amount) { + savingsAccountBalance -= amount; + primaryAccountBalance += amount; + return true; + } + } + } + return false; + } +} diff --git a/java/ql/src/Security/CWE/CWE-833/LockOrderInconsistency.qhelp b/java/ql/src/Security/CWE/CWE-833/LockOrderInconsistency.qhelp new file mode 100644 index 00000000000..318a9c541dc --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-833/LockOrderInconsistency.qhelp @@ -0,0 +1,61 @@ + + + + +

    +Acquiring two locks in an inconsistent order may result in deadlock if two threads simultaneously +attempt to acquire the locks, and each thread succeeds in acquiring one lock prior to being able +to acquire the other lock. +

    +
    + + +

    +To guard against deadlock, use one of the following alternatives: +

    + +
      +
    • Define an ordering on locks, and ensure that clients respect that ordering when acquiring locks.
    • +
    • Simplify the code to only use a single lock, where possible.
    • +
    • Use ReentrantLocks and acquire locks using tryLock() instead of lock().
    • +
    + +
    + + +

    +In the following example, one method acquires primaryLock followed by savingsLock, and +another method acquires these locks in reverse order. This may result in deadlock if the two methods are invoked by +two threads simultaneously, and each thread acquires one of the two locks prior to being able to acquire the other one. +

    + + + +

    +One way to address the issue in the above example is to reverse the lock order in transferToPrimary +to match the lock order in transferToSecondary. +

    + +
    + + + +
  • The CERT Oracle Secure Coding Standard for Java: + LCK07-J. Avoid deadlock by requesting and releasing locks in the same order.
  • +
  • + Java Language Specification: + Synchronization.
  • +
  • + Java API Documentation: + ReentrantLock.
  • + + + + + +
    + +
    diff --git a/java/ql/src/Security/CWE/CWE-833/LockOrderInconsistency.ql b/java/ql/src/Security/CWE/CWE-833/LockOrderInconsistency.ql new file mode 100644 index 00000000000..a2c91957722 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-833/LockOrderInconsistency.ql @@ -0,0 +1,173 @@ +/** + * @name Lock order inconsistency + * @description Acquiring multiple locks in a different order may cause deadlock. + * @kind problem + * @problem.severity recommendation + * @precision medium + * @id java/lock-order-inconsistency + * @tags security + * external/cwe/cwe-833 + */ +import java + +/** A variable of type `ReentrantLock`. */ +class LockVariable extends Variable { + LockVariable() { + getType().(RefType).hasQualifiedName("java.util.concurrent.locks", "ReentrantLock") + } + + /** An access to method `lock` on this variable. */ + MethodAccess getLockAction() { + exists(MethodAccess ma | ma.getQualifier() = this.getAnAccess() | + ma.getMethod().hasName("lock") and + result = ma + ) + } +} + +/** A synchronized method or statement, or an expression statement containing an access to a synchronized method. */ +class Synched extends Top { + Synched() { + this instanceof SynchronizedStmt or + exists(Method m | m.isSynchronized() and not m.isStatic() | + m = this or + exists(MethodAccess ma, VarAccess qual | ma = this and qual = ma.getQualifier() | ma.getMethod() = m) + ) + } + + /** A synchronizing statement nested within this element. */ + Synched getInnerSynch() { + result = this.(Method).getBody().getAChild*() or + result = this.(SynchronizedStmt).getAChild+() or + exists(MethodAccess ma | ma = result | ma.getEnclosingStmt().getParent*() = this) + } + + /** The variable on which synchronization is performed, provided this element is a `SynchronizedStmt`. */ + Variable getLockVar() { + exists(VarAccess va | va = this.(SynchronizedStmt).getExpr() and not exists(va.getQualifier()) | + result = va.getVariable() + ) + } + + /** + * The type of the instance on which synchronization is performed, provided this element is a + * synchronized method or method access. + */ + RefType getLockType() { + result = this.(Method).getDeclaringType().getSourceDeclaration() or + result = this.(MethodAccess).getMethod().getDeclaringType().getSourceDeclaration() + } +} + +/** + * In one situation, a `ReentrantLock` is obtained on one variable in `first` + * and then on another variable in `second`, but elsewhere, the lock order is reversed + * by first obtaining a lock on the latter variable in `otherFirst`. + */ +predicate badReentrantLockOrder(MethodAccess first, MethodAccess second, MethodAccess otherFirst) { + exists(LockVariable v1, LockVariable v2, MethodAccess otherSecond | + first = v1.getLockAction() and otherSecond = v1.getLockAction() and + second = v2.getLockAction() and otherFirst = v2.getLockAction() and + first.(ControlFlowNode).getASuccessor+() = second and + otherFirst.(ControlFlowNode).getASuccessor+() = otherSecond + | + v1 != v2 + ) +} + +/** + * In one situation, two synchronized statements `outer` and `inner` obtain locks + * on different variables in one order, and elsewhere, the lock order is reversed, + * starting with `otherOuter`. + */ +predicate badSynchronizedStmtLockOrder(Expr outerExpr, Expr innerExpr, Expr otherOuterExpr) { + exists(Synched outer, Synched inner, Synched otherOuter | + outer.(SynchronizedStmt).getExpr() = outerExpr and + inner.(SynchronizedStmt).getExpr() = innerExpr and + otherOuter.(SynchronizedStmt).getExpr() = otherOuterExpr and + inner = outer.getInnerSynch() and + exists(Variable v1, Variable v2 | v1 = outer.getLockVar() and v2 = inner.getLockVar() and v1 != v2 | + exists(Synched otherInner | otherInner = otherOuter.getInnerSynch() | + v2 = otherOuter.getLockVar() and + v1 = otherInner.getLockVar() + ) + ) + ) +} + +/** + * The method access `ma` to method `m` is qualified by an access to variable `vQual` + * and has an access to variable `vArg` as the argument at index `i`. + */ +predicate qualifiedMethodAccess(MethodAccess ma, Method m, Variable vQual, int i, Variable vArg) { + ma.getMethod() = m and + ma.getQualifier().(VarAccess).getVariable() = vQual and + ma.getArgument(i).(VarAccess).getVariable() = vArg +} + +/** + * Holds if the specified method accesses occur on different branches of the same conditional statement + * inside an unsynchronized method. + */ +predicate inDifferentBranches(MethodAccess ma1, MethodAccess ma2) { + exists(IfStmt cond | + ma1.getEnclosingStmt() = cond.getThen().getAChild*() and + ma2.getEnclosingStmt() = cond.getElse().getAChild*() and + not cond.getEnclosingCallable().isSynchronized() + ) +} + +/** The method access `ma` occurs in method `runnable`, which is an implementation of `Runnable.run()`. */ +predicate inRunnable(MethodAccess ma, Method runnable) { + runnable.getName() = "run" and + runnable.getDeclaringType().getASupertype+().hasQualifiedName("java.lang", "Runnable") and + ma.getEnclosingCallable() = runnable +} + +/** + * Holds if the specified method accesses occur in different `Runnable.run()` methods, + * indicating that they may be invoked by different threads. + */ +predicate inDifferentRunnables(MethodAccess ma1, MethodAccess ma2) { + exists(Method runnable1, Method runnable2 | + inRunnable(ma1, runnable1) and + inRunnable(ma2, runnable2) and + runnable1 != runnable2 + ) +} + +/** + * A synchronized method `outer` accessed at `outerAccess` makes a synchronized method access + * in statement `inner` that is qualified by one of the parameters of `outer`, and there is + * another access to `outer` that may cause locking to be performed in a different order. + */ +predicate badMethodAccessLockOrder(MethodAccess outerAccess, MethodAccess innerAccess, MethodAccess other) { + exists(Synched outer, Synched inner | + inner.(MethodAccess) = innerAccess and + inner = outer.getInnerSynch() and + inner.getLockType() = outer.getLockType() and + exists(Parameter p, int i | outer.(Method).getAParameter() = p and p.getPosition() = i | + inner.(MethodAccess).getQualifier().(VarAccess).getVariable() = p and + exists(MethodAccess ma1, MethodAccess ma2, Variable v1, Variable v2 | + qualifiedMethodAccess(ma1, outer, v1, i, v2) and + qualifiedMethodAccess(ma2, outer, v2, i, v1) and + v1 != v2 and + ( + inDifferentBranches(ma1, ma2) or + inDifferentRunnables(ma1, ma2) + ) and + ma1 = outerAccess and + ma2 = other + ) + ) + ) +} + +from Expr first, Expr second, Expr other +where + badReentrantLockOrder(first, second, other) or + badSynchronizedStmtLockOrder(first, second, other) or + badMethodAccessLockOrder(first, second, other) +select first, "Synchronization here and $@ may be performed in reverse order starting $@ and result in deadlock.", + second, "here", + other, "here" diff --git a/java/ql/src/Security/CWE/CWE-835/InfiniteLoop.qhelp b/java/ql/src/Security/CWE/CWE-835/InfiniteLoop.qhelp new file mode 100644 index 00000000000..6ee07164bd7 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-835/InfiniteLoop.qhelp @@ -0,0 +1,49 @@ + + + + +

    +Loops can contain multiple exit conditions, either directly in the loop +condition or as guards around break or return +statements. If an exit condition cannot be satisfied, then the code is +misleading at best, and the loop might not terminate. +

    +
    + + +

    +When writing a loop that is intended to terminate, make sure that all the +necessary exit conditions can be satisfied and that loop termination is clear. +

    +
    + + + +

    +The following example shows a potentially infinite loop, since the inner loop +condition is constantly true. Of course, the loop may or may not be infinite +depending on the behavior of shouldBreak, but if this was +intended as the only exit condition the loop should be rewritten to make this +clear. +

    + + + +

    +To fix the loop the condition is corrected to check the right variable. +

    + + + +
    + + +
  • +Java Language Specification: +Blocks and Statements. +
  • + +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-835/InfiniteLoop.ql b/java/ql/src/Security/CWE/CWE-835/InfiniteLoop.ql new file mode 100644 index 00000000000..10059b64ed6 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-835/InfiniteLoop.ql @@ -0,0 +1,57 @@ +/** + * @name Loop with unreachable exit condition + * @description An iteration or loop with an exit condition that cannot be + * reached is an indication of faulty logic and can likely lead to infinite + * looping. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/unreachable-exit-in-loop + * @tags security + * external/cwe/cwe-835 + */ + +import java +import Likely_Bugs.Comparison.UselessComparisonTest + +/** + * The condition `cond` is a loop condition for `loop` - potentially indirectly + * by guarding a `break` or a `return` that can exit the loop. + */ +predicate loopCondition(LoopStmt loop, Expr cond, boolean polarity) { + polarity = true and cond = loop.getCondition() or + exists(IfStmt ifstmt, Stmt exit | + ifstmt.getParent*() = loop.getBody() and + ifstmt.getCondition() = cond and + ( exit.(BreakStmt).(JumpStmt).getTarget() = loop or + exit.(ReturnStmt).getParent*() = loop.getBody() + ) and + ( polarity = false and exit.getParent*() = ifstmt.getThen() or + polarity = true and exit.getParent*() = ifstmt.getElse() + ) + ) +} + +/** + * The expression `subcond` is a (reflexive, transitive) sub-expression of + * `cond` passing through only logical connectives. The boolean `negated` + * indicates whether an odd number of negations separates `cond` and `subcond`. + */ +predicate subCondition(Expr cond, Expr subcond, boolean negated) { + cond = subcond and negated = false or + subCondition(cond.(AndLogicalExpr).getAnOperand(), subcond, negated) or + subCondition(cond.(OrLogicalExpr).getAnOperand(), subcond, negated) or + subCondition(cond.(ParExpr).getExpr(), subcond, negated) or + subCondition(cond.(LogNotExpr).getExpr(), subcond, negated.booleanNot()) +} + +from LoopStmt loop, BinaryExpr test, boolean testIsTrue, Expr cond, boolean polarity, boolean negated +where + loopCondition(loop, cond, polarity) and + not loop instanceof EnhancedForStmt and + subCondition(cond, test, negated) and + uselessTest(_, test, testIsTrue) and + testIsTrue = polarity.booleanXor(negated) +select + loop, "Loop might not terminate, as termination depends in part on $@ being " + + testIsTrue.booleanNot() + " but it is always " + testIsTrue + ".", test, "this test" diff --git a/java/ql/src/Security/CWE/CWE-835/InfiniteLoopBad.java b/java/ql/src/Security/CWE/CWE-835/InfiniteLoopBad.java new file mode 100644 index 00000000000..69e13801c22 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-835/InfiniteLoopBad.java @@ -0,0 +1,6 @@ +for (int i=0; i<10; i++) { + for (int j=0; i<10; j++) { + // do stuff + if (shouldBreak()) break; + } +} diff --git a/java/ql/src/Security/CWE/CWE-835/InfiniteLoopGood.java b/java/ql/src/Security/CWE/CWE-835/InfiniteLoopGood.java new file mode 100644 index 00000000000..06c18c4e68b --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-835/InfiniteLoopGood.java @@ -0,0 +1,6 @@ +for (int i=0; i<10; i++) { + for (int j=0; j<10; j++) { + // do stuff + if (shouldBreak()) break; + } +} diff --git a/java/ql/src/Violations of Best Practice/Boolean Logic/SimplifyBoolExpr.java b/java/ql/src/Violations of Best Practice/Boolean Logic/SimplifyBoolExpr.java new file mode 100644 index 00000000000..b8b7b3f3e79 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Boolean Logic/SimplifyBoolExpr.java @@ -0,0 +1,30 @@ +boolean f1(List l) { + for(Boolean x : l) { + if (x == true) return true; + } + return false; +} + +boolean f2(List l) { + for(Boolean x : l) { + if (x) return true; + } + return false; +} + +void g1(List l1) { + List l2 = new ArrayList(); + for(Boolean x : l1) { + l2.add(x == true); + } +} + +void g2(List l1) { + List l2 = new ArrayList(); + for(Boolean x : l1) { + if (x == null) { + // handle null case + } + l2.add(x); + } +} diff --git a/java/ql/src/Violations of Best Practice/Boolean Logic/SimplifyBoolExpr.qhelp b/java/ql/src/Violations of Best Practice/Boolean Logic/SimplifyBoolExpr.qhelp new file mode 100644 index 00000000000..949f57ce7a7 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Boolean Logic/SimplifyBoolExpr.qhelp @@ -0,0 +1,91 @@ + + + + +

    +There are a number of boolean expression patterns that can easily be rewritten +to make them simpler. +Boolean expressions involving comparisons with boolean literals, +ternary conditionals with a boolean literal as one of the results, +double negations, or negated comparisons can all be changed to +equivalent and simpler expressions. +

    +
    + + +

    +If A and B are expressions of boolean type, you can +simplify them using the rewrites shown below. These rewrites are equally suitable +for the primitive type boolean and the boxed type Boolean. +

    + + + + + + + + + + + + + +
    ExpressionSimplified expression
    A == trueA(*)
    A != falseA(*)
    A == false!A
    A != true!A
    A ? true : BA || B
    A ? B : falseA && B
    A ? B : true!A || B
    A ? false : B!A && B
    A ? true : falseA(*)
    A ? false : true!A
    !!AA(*)
    +

    +Note that all of these rewrites yield completely equivalent code, except +possibly for those marked with (*) when A has type Boolean. +These can depend on the context if null +values are involved. This is because a comparison or test of a +Boolean will perform an automatic unboxing conversion that throws a +NullPointerException if null is encountered, so the rewrites marked +(*) are only completely equivalent if the surrounding context of the expression unboxes the value, for +example by occurring as the condition in an if statement. +

    + +

    +In addition to the rewrites above, negated comparisons can also be simplified in the following way: +

    + + + + + + + + +
    ExpressionSimplified expression
    !(A == B)A != B
    !(A != B)A == B
    !(A < B)A >= B
    !(A > B)A <= B
    !(A <= B)A > B
    !(A >= B)A < B
    + +
    + + +

    +In the following example the method f2 is a straightforward +simplification of f1; they will both throw a NullPointerException if +a null is encountered. +A similar rewrite of g1 would however result in a method with a different meaning, +and a rewrite along the lines of g2 might be more appropriate. + +In any case, care should be taken to ensure correct behavior for +null values when the boxed type Boolean is used. +

    + + +
    + + + +
  • +The Java Language Specification: +Logical Complement Operator !, +Boolean Equality Operators == and !=, +Conditional-And Operator &&, +Conditional-Or Operator ||, +Conditional Operator ? :. +
  • + +
    + +
    diff --git a/java/ql/src/Violations of Best Practice/Boolean Logic/SimplifyBoolExpr.ql b/java/ql/src/Violations of Best Practice/Boolean Logic/SimplifyBoolExpr.ql new file mode 100644 index 00000000000..30d88f7c4e5 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Boolean Logic/SimplifyBoolExpr.ql @@ -0,0 +1,75 @@ +/** + * @name Unnecessarily complex boolean expression + * @description Boolean expressions that are unnecessarily complicated hinder readability. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/complex-boolean-expression + * @tags readability + */ + +import java + +class BoolCompare extends EqualityTest { + BoolCompare() { + this.getAnOperand() instanceof BooleanLiteral + } + + predicate simplify(string pattern, string rewrite) { + exists(boolean b | b = this.getAnOperand().(BooleanLiteral).getBooleanValue() | + this instanceof EQExpr and b = true and pattern = "A == true" and rewrite = "A" or + this instanceof NEExpr and b = false and pattern = "A != false" and rewrite = "A" or + this instanceof EQExpr and b = false and pattern = "A == false" and rewrite = "!A" or + this instanceof NEExpr and b = true and pattern = "A != true" and rewrite = "!A" + ) + } +} + +predicate conditionalWithBool(ConditionalExpr c, string pattern, string rewrite) { + exists(boolean truebranch | + c.getTrueExpr().(BooleanLiteral).getBooleanValue() = truebranch and + not c.getFalseExpr() instanceof BooleanLiteral and + not c.getFalseExpr().getType() instanceof NullType and + ( truebranch = true and pattern = "A ? true : B" and rewrite = "A || B" or + truebranch = false and pattern = "A ? false : B" and rewrite = "!A && B" + ) + ) or + exists(boolean falsebranch | + not c.getTrueExpr() instanceof BooleanLiteral and + not c.getTrueExpr().getType() instanceof NullType and + c.getFalseExpr().(BooleanLiteral).getBooleanValue() = falsebranch and + ( falsebranch = true and pattern = "A ? B : true" and rewrite = "!A || B" or + falsebranch = false and pattern = "A ? B : false" and rewrite = "A && B" + ) + ) or + exists(boolean truebranch, boolean falsebranch | + c.getTrueExpr().(BooleanLiteral).getBooleanValue() = truebranch and + c.getFalseExpr().(BooleanLiteral).getBooleanValue() = falsebranch and + ( truebranch = true and falsebranch = false and pattern = "A ? true : false" and rewrite = "A" or + truebranch = false and falsebranch = true and pattern = "A ? false : true" and rewrite = "!A" + ) + ) +} + +class ComparisonOrEquality extends BinaryExpr { + ComparisonOrEquality() { + this instanceof ComparisonExpr or this instanceof EqualityTest + } + + predicate negate(string pattern, string rewrite) { + this instanceof EQExpr and pattern = "!(A == B)" and rewrite = "A != B" or + this instanceof NEExpr and pattern = "!(A != B)" and rewrite = "A == B" or + this instanceof LTExpr and pattern = "!(A < B)" and rewrite = "A >= B" or + this instanceof GTExpr and pattern = "!(A > B)" and rewrite = "A <= B" or + this instanceof LEExpr and pattern = "!(A <= B)" and rewrite = "A > B" or + this instanceof GEExpr and pattern = "!(A >= B)" and rewrite = "A < B" + } +} + +from Expr e, string pattern, string rewrite +where + e.(BoolCompare).simplify(pattern, rewrite) or + conditionalWithBool(e, pattern, rewrite) or + e.(LogNotExpr).getExpr().getProperExpr().(ComparisonOrEquality).negate(pattern, rewrite) or + e.(LogNotExpr).getExpr().getProperExpr() instanceof LogNotExpr and pattern = "!!A" and rewrite = "A" +select e, "Expressions of the form \"" + pattern + "\" can be simplified to \"" + rewrite + "\"." diff --git a/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariable.qhelp b/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariable.qhelp new file mode 100644 index 00000000000..ff8b11224fa --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariable.qhelp @@ -0,0 +1,67 @@ + + + + +

    +In Java all of the primitive types have boxed counterparts. The boxed types +are objects and can therefore be null, whereas the primitive types +can never be null. The names of the primitive and boxed types are +similar except that primitive types start with a lower-case letter and boxed +types start with an upper-case letter (also, for char and +int the names of the boxed types are slightly longer, namely +Character and Integer). +

    +

    +Because the names are so similar and because Java performs automatic +boxing and unboxing conversions, they can easily be confused. Furthermore, using +a boxed type where a primitive type was intended leads to both +readability issues and potentially superfluous allocation of objects. +

    +
    + + +

    +If a variable is never assigned null it should use the primitive +type, as this both directly shows the impossibility of null and +also avoids unnecessary boxing and unboxing conversions. +

    + +
    + + +

    +In the example below the variable done controls the loop exit. It +is only set to false before the loop entry and set to +true at some point during the loop iteration. +

    + + + +

    +Each of the assignments to done involves a boxing conversion and +the check involves an unboxing conversion. Since done is never +null, these conversions can be completely avoided, and the code +made clearer, by using the primitive type instead. Therefore the code should +be rewritten in the following way: +

    + + +
    + + + +
  • +The Java Language Specification: +Boxing Conversion, +Unboxing Conversion. +
  • +
  • +The Java Tutorials: +Autoboxing and Unboxing. +
  • + +
    + +
    diff --git a/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariable.ql b/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariable.ql new file mode 100644 index 00000000000..58efbf84b9e --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariable.ql @@ -0,0 +1,71 @@ +/** + * @name Boxed variable is never null + * @description Using a boxed type for a variable that is never assigned 'null' + * hinders readability because it implies that 'null' is a potential value. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id java/non-null-boxed-variable + * @tags readability + * types + */ +import java + +class LocalBoxedVar extends LocalVariableDecl { + LocalBoxedVar() { + this.getType() instanceof BoxedType + } + + PrimitiveType getPrimitiveType() { + this.getType().(BoxedType).getPrimitiveType() = result + } +} + +/** + * If a primitive value always occurs in a boxed context (and maybe more than once for each assigned value), then + * declaring the type as a boxed type merely performs the boxing up front and is likely deliberate. + * + * As this pattern will never have local boxing-followed-by-unboxing sequences, and may in fact save + * some number of boxing operations, these cases are excluded. + */ +predicate notDeliberatelyBoxed(LocalBoxedVar v) { + not forall(RValue a | a = v.getAnAccess() | + exists(Call c, int i | + c.getCallee().getParameterType(i) instanceof RefType and + c.getArgument(i) = a + ) or + exists(ReturnStmt ret | + ret.getResult() = a and + ret.getEnclosingCallable().getReturnType() instanceof RefType + ) + ) +} + +/** + * Replacing the type of a boxed variable with the corresponding primitive type may affect + * overload resolution. If this is the case then the boxing is most likely intentional and + * it should not be reported as a violation. + */ +predicate affectsOverload(LocalBoxedVar v) { + exists(Call call, int i, Callable c1, Callable c2 | + call.getCallee() = c1 and + call.getArgument(i) = v.getAnAccess() and + c1.getDeclaringType() = c2.getDeclaringType() and + c1.getParameterType(i) instanceof RefType and + c2.getParameterType(i) instanceof PrimitiveType and + c1.getName() = c2.getName() and + c1.getNumberOfParameters() = c2.getNumberOfParameters() + ) +} + +from LocalBoxedVar v +where + forall(Expr e | e = v.getAnAssignedValue() | e.getType() = v.getPrimitiveType()) and + ( not v.getDeclExpr().getParent() instanceof EnhancedForStmt or + v.getDeclExpr().getParent().(EnhancedForStmt).getExpr().getType().(Array).getComponentType() = v.getPrimitiveType() + ) and + notDeliberatelyBoxed(v) and + not affectsOverload(v) +select v, + "The variable '" + v.getName() + "' is only assigned values of primitive type and is never 'null', but it is declared with the boxed type '" + + v.getType().toString() + "'." diff --git a/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariableBad.java b/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariableBad.java new file mode 100644 index 00000000000..4f44819d209 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariableBad.java @@ -0,0 +1,6 @@ +Boolean done = false; +while (!done) { + // ... + done = true; + // ... +} diff --git a/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariableGood.java b/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariableGood.java new file mode 100644 index 00000000000..d95c90ec350 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariableGood.java @@ -0,0 +1,6 @@ +boolean done = false; +while (!done) { + // ... + done = true; + // ... +} diff --git a/java/ql/src/Violations of Best Practice/Comments/CommentedCode.qhelp b/java/ql/src/Violations of Best Practice/Comments/CommentedCode.qhelp new file mode 100644 index 00000000000..4ce0ee029b6 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Comments/CommentedCode.qhelp @@ -0,0 +1,7 @@ + + + + + diff --git a/java/ql/src/Violations of Best Practice/Comments/CommentedCode.ql b/java/ql/src/Violations of Best Practice/Comments/CommentedCode.ql new file mode 100644 index 00000000000..96511a75d79 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Comments/CommentedCode.ql @@ -0,0 +1,17 @@ +/** + * @name Commented-out code + * @description Commented-out code makes the remaining code more difficult to read. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/commented-out-code + * @tags maintainability + * readability + * statistical + * non-attributable + */ + +import CommentedCode + +from CommentedOutCode comment +select comment, "This comment appears to contain commented-out code." diff --git a/java/ql/src/Violations of Best Practice/Comments/CommentedCode.qll b/java/ql/src/Violations of Best Practice/Comments/CommentedCode.qll new file mode 100644 index 00000000000..89fee285ca9 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Comments/CommentedCode.qll @@ -0,0 +1,136 @@ +import java +import semmle.code.java.frameworks.gwt.GWT +import semmle.code.java.frameworks.j2objc.J2ObjC + +/** + * Guess if the given `JavadocText` is a line of code. + * + * Matches comment lines ending with `{`, `}` or `;` that do not start with `>` or `@`, but first filters out: + * + * - Lines containing `//` + * - Substrings between `{@` and `}` (including the brackets themselves) + * - HTML entities in common notation (e.g. `>` and `é`) + * - HTML entities in decimal notation (e.g. `̀`) + * - HTML entities in hexadecimal notation (e.g. `灟`) + */ +private predicate looksLikeCode(JavadocText line) { + exists(string trimmed | + trimmed = trimmedCommentText(line) + | + ( + trimmed.matches("%;") or + trimmed.matches("%{") or + trimmed.matches("%}") + ) and + not trimmed.matches(">%") and + not trimmed.matches("@%") + ) +} + +/** + * Remove things from comments that may look like code but are not code: + * + * - Lines containing `//` + * - Substrings between `{@` and `}` (including the brackets themselves) + * - HTML entities in common notation (e.g. `>` and `é`) + * - HTML entities in decimal notation (e.g. `̀`) + * - HTML entities in hexadecimal notation (e.g. `灟`) + */ +private string trimmedCommentText(JavadocText line) { + result = line.getText().trim() + .regexpReplaceAll("\\s*//.*$", "") + .regexpReplaceAll("\\{@[^}]+\\}", "") + .regexpReplaceAll("(?i)&#?[a-z0-9]{1,31};", "") +} + +/** + * Holds if this comment contains opening and closing `` or `
    ` tags.
    + */
    +private predicate hasCodeTags(Javadoc j) {
    +  exists(string tag | tag = "pre" or tag = "code" |
    +    j.getAChild().(JavadocText).getText().matches("%<" + tag + ">%") and
    +    j.getAChild().(JavadocText).getText().matches("%%")
    +  )
    +}
    +
    +/**
    + * The comment immediately following `c`.
    + */
    +private Javadoc getNextComment(Javadoc c) {
    +  exists(int n, File f | javadocLines(c, f, _, n) |
    +    javadocLines(result, f, n+1, _)
    +  )
    +}
    +
    +private predicate javadocLines(Javadoc j, File f, int start, int end) {
    +  f = j.getFile() and
    +  start = j.getLocation().getStartLine() and
    +  end = j.getLocation().getEndLine()
    +}
    +
    +private class JavadocFirst extends Javadoc {
    +  JavadocFirst() {
    +    not exists(Javadoc prev | this = getNextComment(prev))
    +  }
    +}
    +
    +/**
    + * The number of lines that look like code in the comment `first`, or ones that follow it.
    + */
    +private int codeCount(JavadocFirst first) {
    +  result = sum(Javadoc following |
    +    following = getNextComment*(first) and not hasCodeTags(following)
    +    |
    +    count(JavadocText line | line = following.getAChild() and looksLikeCode(line))
    +  )
    +}
    +
    +/**
    + * The number of lines in the comment `first`, or ones that follow it.
    + */
    +private int anyCount(JavadocFirst first) {
    +  result = sum(Javadoc following |
    +    following = getNextComment*(first) and not hasCodeTags(following)
    +    |
    +    count(JavadocText line | line = following.getAChild() and
    +      not exists(string trimmed | trimmed = line.getText().trim() |
    +        trimmed.regexpMatch("(|/\\*|/\\*\\*|\\*|\\*/)") or
    +        trimmed.matches("@%")
    +      )
    +    )
    +  )
    +}
    +
    +/**
    + * A piece of commented-out code, identified using heuristics.
    + */
    +class CommentedOutCode extends JavadocFirst {
    +  CommentedOutCode() {
    +    anyCount(this) > 0 and
    +    ((float)codeCount(this))/((float)anyCount(this)) > 0.5 and
    +    not this instanceof JSNIComment and
    +    not this instanceof OCNIComment
    +  }
    +
    +  /**
    +   * The number of lines that appear to be commented-out code.
    +   */
    +  int getCodeLines(){
    +    result = codeCount(this)
    +  }
    +
    +  private Javadoc getLastSuccessor() {
    +    result = getNextComment*(this) and
    +    not exists(getNextComment(result))
    +  }
    +
    +  override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
    +    path = getLocation().getFile().getAbsolutePath() and
    +    sl = getLocation().getStartLine() and
    +    sc = getLocation().getStartColumn() and
    +    exists(Location end | end = this.getLastSuccessor().getLocation() |
    +      el = end.getEndLine() and
    +      ec = end.getEndColumn()
    +    )
    +  }
    +}
    diff --git a/java/ql/src/Violations of Best Practice/Comments/TodoComments.qhelp b/java/ql/src/Violations of Best Practice/Comments/TodoComments.qhelp
    new file mode 100644
    index 00000000000..b1a01ba7163
    --- /dev/null
    +++ b/java/ql/src/Violations of Best Practice/Comments/TodoComments.qhelp	
    @@ -0,0 +1,53 @@
    +
    +
    +
    +
    +
    +

    A comment that includes the word TODO or FIXME often marks a part of +the code that is incomplete or broken, or highlights ambiguities in the +software's specification.

    + +

    For example, this list of comments is typical of those found in real +programs:

    + +
      +
    • TODO: move this code somewhere else
    • +
    • FIXME: handle this case
    • +
    • FIXME: find a better solution to this workaround
    • +
    • TODO: test this
    • +
    + +
    + + +

    It is very important that TODO or FIXME comments are +not just removed from the code. Each of them must be addressed in some way.

    + +

    Simpler comments can usually be immediately addressed by fixing the code, +adding a test, doing some refactoring, or clarifying the intended behavior of +a feature.

    + +

    In contrast, larger issues may require discussion, and a significant amount +of work to address. In these cases it is a good idea to move the comment to an +issue-tracking system, so that the issue can be tracked +and prioritized relative to other defects and feature requests.

    + +
    + + + +
  • +Approxion: + TODO or not TODO. +
  • +
  • +Wikipedia: +Comment tags, +Issue tracking system. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Comments/TodoComments.ql b/java/ql/src/Violations of Best Practice/Comments/TodoComments.ql new file mode 100644 index 00000000000..430d23cb4b6 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Comments/TodoComments.ql @@ -0,0 +1,19 @@ +/** + * @name TODO/FIXME comments + * @description A comment that contains 'TODO' or 'FIXME' may indicate code that is incomplete or + * broken, or it may highlight an ambiguity in the software's specification. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/todo-comment + * @tags maintainability + * readability + * external/cwe/cwe-546 + */ +import java + +from JavadocText c +where + c.getText().matches("%TODO%") or + c.getText().matches("%FIXME%") +select c, "TODO/FIXME comment." diff --git a/java/ql/src/Violations of Best Practice/Dead Code/AssignmentInReturn.java b/java/ql/src/Violations of Best Practice/Dead Code/AssignmentInReturn.java new file mode 100644 index 00000000000..4db695bf22f --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/AssignmentInReturn.java @@ -0,0 +1,7 @@ +public class Utilities +{ + public static int max(int a, int b, int c) { + int ret = Math.max(a, b) + return ret = Math.max(ret, c); // Redundant assignment + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Dead Code/AssignmentInReturn.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/AssignmentInReturn.qhelp new file mode 100644 index 00000000000..bb88ab26d95 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/AssignmentInReturn.qhelp @@ -0,0 +1,44 @@ + + + + + +

    An assignment is an expression. The value of an assignment expression +is the value assigned to the variable. This can be useful, for example, when +initializing two or more variables at once (for example, a = b = 0;). +However, assigning to a local variable in the expression of a return statement +is redundant because that value can never be read.

    + +
    + + +

    Remove the redundant assignment from the return statement, leaving just the +right-hand side of the assignment.

    + +
    + + +

    In the following example, consider the second assignment to ret. The variable goes +out of scope when the method returns, and the value assigned to it is never read. Therefore, +the assignment is redundant. Instead, the last line of the method can be changed to +return Math.max(ret, c);

    + + + +
    + + + +
  • +Java Language Specification: + +14.17 The return Statement, + +15.26 Assignment Operators. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Dead Code/AssignmentInReturn.ql b/java/ql/src/Violations of Best Practice/Dead Code/AssignmentInReturn.ql new file mode 100644 index 00000000000..b0789b23816 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/AssignmentInReturn.ql @@ -0,0 +1,17 @@ +/** + * @name Assignment in return statement + * @description Assigning to a local variable in a 'return' statement has no effect. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/assignment-in-return + * @tags maintainability + * readability + */ +import java + +from AssignExpr e, LocalVariableDecl v +where + e.getDest().(VarAccess).getVariable() = v and + e.getParent+() instanceof ReturnStmt +select e, "Assignment to a local variable in a return statement may have no effect." diff --git a/java/ql/src/Violations of Best Practice/Dead Code/CreatesEmptyZip.java b/java/ql/src/Violations of Best Practice/Dead Code/CreatesEmptyZip.java new file mode 100644 index 00000000000..ab777097cf8 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/CreatesEmptyZip.java @@ -0,0 +1,21 @@ +class Archive implements Closeable +{ + private ZipOutputStream zipStream; + + public Archive(File zip) throws IOException { + OutputStream stream = new FileOutputStream(zip); + stream = new BufferedOutputStream(stream); + zipStream = new ZipOutputStream(stream); + } + + public void archive(String name, byte[] content) throws IOException { + ZipEntry entry = new ZipEntry(name); + zipStream.putNextEntry(entry); + // Missing call to 'write' + zipStream.closeEntry(); + } + + public void close() throws IOException { + zipStream.close(); + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Dead Code/CreatesEmptyZip.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/CreatesEmptyZip.qhelp new file mode 100644 index 00000000000..b7e6dc4611e --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/CreatesEmptyZip.qhelp @@ -0,0 +1,45 @@ + + + + + +

    The ZipOutputStream class is used to write ZIP files to a file +or other stream. A ZIP file consists of a number of entries. Usually +each entry corresponds to a file in the directory structure being zipped. There +is a method on ZipOutputStream that is slightly confusingly named +putNextEntry. Despite its name, it does not write a whole entry. +Instead, it writes the metadata for an entry. The content for that entry +is then written using the write method. Finally the entry is +closed using closeEntry.

    + +

    Therefore, if you call putNextEntry and closeEntry but omit the +call to write, an empty ZIP file entry is written to the output stream.

    + +
    + + +

    Ensure that you include a call to ZipOutputStream.write.

    + +
    + + +

    In the following example, the archive method calls putNextEntry and +closeEntry but the call to write is left out.

    + + + +
    + + + +
  • +Java 2 Platform Standard Edition 5.0, API Specification: + +ZipOutputStream. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Dead Code/CreatesEmptyZip.ql b/java/ql/src/Violations of Best Practice/Dead Code/CreatesEmptyZip.ql new file mode 100644 index 00000000000..abeaae0a60a --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/CreatesEmptyZip.ql @@ -0,0 +1,55 @@ +/** + * @name Creates empty ZIP file entry + * @description Omitting a call to 'ZipOutputStream.write' when writing a ZIP file to an output + * stream means that an empty ZIP file entry is written. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/empty-zip-file-entry + * @tags reliability + * readability + */ +import java +import semmle.code.java.dataflow.SSA + +/** A class that is a descendant of `java.util.zip.ZipOutputStream`. */ +class ZipOutputStream extends Class { + ZipOutputStream() { + exists(Class zip | zip.hasQualifiedName("java.util.zip", "ZipOutputStream") | + this.hasSupertype*(zip) + ) + } + + Method putNextEntry() { + ( + result.getDeclaringType() = this or + this.inherits(result) + ) and + result.getName() = "putNextEntry" and + result.getNumberOfParameters() = 1 and + result.getAParamType().(Class).hasQualifiedName("java.util.zip", "ZipEntry") + } + + Method closeEntry() { + ( + result.getDeclaringType() = this or + this.inherits(result) + ) and + result.getName() = "closeEntry" and + result.getNumberOfParameters() = 0 + } +} + +from ZipOutputStream jos, MethodAccess putNextEntry, MethodAccess closeEntry, + RValue putNextQualifier, RValue closeQualifier +where + putNextEntry.getMethod() = jos.putNextEntry() and + closeEntry.getMethod() = jos.closeEntry() and + putNextQualifier = putNextEntry.getQualifier() and + closeQualifier = closeEntry.getQualifier() and + adjacentUseUseSameVar(putNextQualifier, closeQualifier) and + not exists(RValue other | + adjacentUseUseSameVar(other, closeQualifier) and + other != putNextQualifier + ) +select closeEntry, "Empty ZIP file entry created." diff --git a/java/ql/src/Violations of Best Practice/Dead Code/DeadLocals.qll b/java/ql/src/Violations of Best Practice/Dead Code/DeadLocals.qll new file mode 100644 index 00000000000..d27c740d35f --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/DeadLocals.qll @@ -0,0 +1,123 @@ +/* + * Provides classes and predicates for "dead locals": which variables are used, which assignments are useless, etc. + */ + +import java +import semmle.code.java.dataflow.SSA +private import semmle.code.java.frameworks.Assertions + +private predicate emptyDecl(SsaExplicitUpdate ssa) { + exists(LocalVariableDeclExpr decl | + decl = ssa.getDefiningExpr() and + not exists(decl.getInit()) and + not exists(EnhancedForStmt for | for.getVariable() = decl) + ) +} + +/** + * A dead SSA variable. Excludes parameters, and phi nodes are never dead, so only includes `VariableUpdate`s. + */ +predicate deadLocal(SsaExplicitUpdate ssa) { + ssa.getSourceVariable().getVariable() instanceof LocalScopeVariable and + not exists(ssa.getAUse()) and not exists(SsaPhiNode phi | phi.getAPhiInput() = ssa) and not exists(SsaImplicitInit init | init.captures(ssa)) and + not emptyDecl(ssa) and + not readImplicitly(ssa, _) +} + +/** + * A dead SSA variable that is expected to be dead as indicated by an assertion. + */ +predicate expectedDead(SsaExplicitUpdate ssa) { + deadLocal(ssa) and + assertFail(ssa.getBasicBlock(), _) +} + +/** + * A dead SSA variable that is overwritten by a live SSA definition. + */ +predicate overwritten(SsaExplicitUpdate ssa) { + deadLocal(ssa) and + exists(SsaExplicitUpdate overwrite | + overwrite.getSourceVariable() = ssa.getSourceVariable() and + not deadLocal(overwrite) and + not overwrite.getDefiningExpr() instanceof LocalVariableDeclExpr and + exists(BasicBlock bb1, BasicBlock bb2, int i, int j | + bb1.getNode(i) = ssa.getCFGNode() and + bb2.getNode(j) = overwrite.getCFGNode() + | + bb1.getABBSuccessor+() = bb2 or + bb1 = bb2 and i < j + ) + ) +} + +/** + * A local variable with a read access. + */ +predicate read(LocalScopeVariable v) { + exists(VarAccess va | va = v.getAnAccess() | va.isRValue()) or + readImplicitly(_, v) +} + +private predicate readImplicitly(SsaExplicitUpdate ssa, LocalScopeVariable v) { + v = ssa.getSourceVariable().getVariable() and + exists(TryStmt try | try.getAResourceVariable() = ssa.getDefiningExpr().getDestVar()) +} + +/** + * A local variable with a write access. + */ +predicate assigned(LocalScopeVariable v) { + exists(VarAccess va | va = v.getAnAccess() | va.isLValue()) +} + +/** + * An expression without side-effects. + */ +predicate exprHasNoEffect(Expr e) { + inInitializer(e) and + not exists(Expr bad | bad = e.getAChildExpr*() | + bad instanceof Assignment or + bad instanceof UnaryAssignExpr or + exists(ClassInstanceExpr cie, Constructor c | + bad = cie and c = cie.getConstructor().getSourceDeclaration() + | + constructorHasEffect(c) + ) or + exists(MethodAccess ma, Method m | + bad = ma and m = ma.getMethod().getAPossibleImplementation() + | + methodHasEffect(m) or not m.fromSource() + ) + ) +} + +private predicate inInitializer(Expr e) { + exists(LocalVariableDeclExpr decl | e = decl.getInit().getAChildExpr*()) +} + +// The next two predicates are somewhat conservative. + +private predicate constructorHasEffect(Constructor c) { + // Only assign fields of the class - do not call methods, + // create new objects or assign any other variables. + exists(MethodAccess ma | ma.getEnclosingCallable() = c) or + exists(ClassInstanceExpr cie | cie.getEnclosingCallable() = c) or + exists(Assignment a | a.getEnclosingCallable() = c | + not exists(VarAccess va | va = a.getDest() | + va.getVariable() instanceof LocalVariableDecl or + exists(Field f | f = va.getVariable() | + va.getQualifier() instanceof ThisAccess or + not exists(va.getQualifier()) + ) + ) + ) +} + +private predicate methodHasEffect(Method m) { + exists(MethodAccess ma | ma.getEnclosingCallable() = m) or + exists(Assignment a | a.getEnclosingCallable() = m) or + exists(ClassInstanceExpr cie | cie.getEnclosingCallable() = m) or + exists(ThrowStmt throw | throw.getEnclosingCallable() = m) or + m.isNative() +} diff --git a/java/ql/src/Violations of Best Practice/Dead Code/DeadRefTypes.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/DeadRefTypes.qhelp new file mode 100644 index 00000000000..c367fcaead7 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/DeadRefTypes.qhelp @@ -0,0 +1,29 @@ + + + + + +

    A non-public class or interface that is not used anywhere +in the program may cause a programmer to waste time and effort maintaining and documenting it.

    + +
    + + +

    Ensure that redundant types are removed from the program.

    + +
    + + + +
  • + Wikipedia: + Unreachable code. +
  • + + + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Dead Code/DeadRefTypes.ql b/java/ql/src/Violations of Best Practice/Dead Code/DeadRefTypes.ql new file mode 100644 index 00000000000..eb2103bbb83 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/DeadRefTypes.ql @@ -0,0 +1,56 @@ +/** + * @name Unused classes and interfaces + * @description A non-public class or interface that is not used anywhere in the program wastes + * programmer resources. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/unused-reference-type + * @tags maintainability + * useless-code + * external/cwe/cwe-561 + */ +import java +import semmle.code.java.Reflection + +/** + * A class or interface that is not used anywhere. + */ +predicate dead(RefType dead) { + dead.fromSource() and + // Nothing depends on this type. + not exists(RefType s | s.getMetrics().getADependency() = dead) and + + // Exclude Struts or JSP classes (marked with Javadoc tags). + not exists(JavadocTag tag, string x | + tag = dead.getDoc().getJavadoc().getATag(x) and + (x.matches("@struts%") or x.matches("@jsp%")) + ) and + // Exclude public types. + not dead.isPublic() and + // Exclude results that have a `main` method. + not dead.getAMethod().hasName("main") and + // Exclude results that are referenced in XML files. + not exists(XMLAttribute xla | xla.getValue() = dead.getQualifiedName()) and + // Exclude type variables. + not dead instanceof BoundedType and + // Exclude JUnit tests. + not dead.getASupertype*().hasName("TestCase") and + // Exclude enum types. + not dead instanceof EnumType and + // Exclude classes that look like they may be reflectively constructed. + not dead.getAnAnnotation() instanceof ReflectiveAccessAnnotation and + + // Insist all source ancestors are dead as well. + forall(RefType t | t.fromSource() and t = dead.getASupertype+() | dead(t)) +} + +from RefType t, string kind +where + dead(t) and + ( + t instanceof Class and kind = "class" or + t instanceof Interface and kind = "interface" + ) +select t, "Unused " + kind + ": " + t.getName() + " is not referenced within this codebase. " + + "If not used as an external API it should be removed." diff --git a/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.java b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.java new file mode 100644 index 00000000000..f0d0c4afbc5 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.java @@ -0,0 +1,7 @@ +Person find(String name) { + Person result; + for (Person p : people.values()) + if (p.getName().equals(name)) + result = p; // Redundant assignment + result = people.get(name); + return result; \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.qhelp new file mode 100644 index 00000000000..6125bef380e --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.qhelp @@ -0,0 +1,35 @@ + + + + + +

    A value is assigned to a local variable, but whenever the variable is subsequently +read, there has been at least one other assignment to that variable. This means +that the original assignment is suspect, because the state of the local variable that +it creates is never used.

    + +
    + +

    Ensure that you check the control and data flow in the method carefully. +If a value is really not needed, consider omitting the assignment. Be careful, +though: if the right-hand side has a side-effect (like performing a method call), +it is important to keep this to preserve the overall behavior.

    + +
    + + +

    In the following example, the value assigned to result on line 5 is always +overwritten (line 6) before being read (line 7). This is a strong indicator that +there is something wrong. By examining the code, we can see that the +loop in lines 3-5 seems to be left over from an old way of storing the list of +persons, and line 6 represents the new (and better-performing) way. Consequently, +we can delete lines 3-5 while preserving behavior.

    + + + + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.ql b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.ql new file mode 100644 index 00000000000..b010500a44b --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.ql @@ -0,0 +1,51 @@ +/** + * @name Assigned value is overwritten + * @description An assignment to a local variable that is not used before a further assignment is + * made has no effect. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/overwritten-assignment-to-local + * @tags maintainability + * useless-code + * readability + * external/cwe/cwe-563 + */ +import java +import DeadLocals + +predicate minusOne(MinusExpr e) { + e.getExpr().(Literal).getValue() = "1" +} + +predicate flowStep(Expr decl, Expr init) { + decl = init or + exists(Field f | f.isFinal() and decl.(FieldAccess).getField() = f | + init = f.getAnAssignedValue() + ) or + decl.(CastExpr).getExpr() = init +} + +predicate excludedInit(Type t, Expr decl) { + exists(Expr init | flowStep(decl, init) | + // The `null` literal for reference types. + t instanceof RefType and init instanceof NullLiteral or + // The default value for primitive types. + init = t.(PrimitiveType).getADefaultValue() or + // The expression `-1` for integral types. + t instanceof IntegralType and minusOne(init) + ) +} + +from VariableUpdate def, LocalScopeVariable v, SsaExplicitUpdate ssa +where + def = ssa.getDefiningExpr() and + v = ssa.getSourceVariable().getVariable() and + deadLocal(ssa) and + not expectedDead(ssa) and + overwritten(ssa) and + not exists(LocalVariableDeclExpr decl | def = decl | + excludedInit(decl.getVariable().getType(), decl.getInit()) + ) +select def, + "This assignment to " + v.getName() + " is useless: the value is always overwritten before it is read." diff --git a/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocalUnread.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocalUnread.qhelp new file mode 100644 index 00000000000..adfce3b0764 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocalUnread.qhelp @@ -0,0 +1,20 @@ + + + + + +

    A value is assigned to a local variable, but the variable is never read subsequently. This means +that the original assignment is suspect, because the state of the local variable that +it creates is never used.

    + +
    + +

    Ensure that you check the control and data flow in the method carefully. +If a value is really not needed, consider omitting the assignment. Be careful, +though: if the right-hand side has a side-effect (like performing a method call), +it is important to keep this to preserve the overall behavior.

    + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocalUnread.ql b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocalUnread.ql new file mode 100644 index 00000000000..46773ddbbd9 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocalUnread.ql @@ -0,0 +1,27 @@ +/** + * @name Useless assignment to local variable + * @description A value is assigned to a local variable, but the local variable + * is only read before the assignment, not after it. + * The assignment has no effect: either it should be removed, + * or the assigned value should be used. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/useless-assignment-to-local + * @tags external/cwe/cwe-561 + */ +import java +import DeadLocals + +from VariableUpdate def, LocalScopeVariable v, SsaExplicitUpdate ssa +where + def = ssa.getDefiningExpr() and + v = ssa.getSourceVariable().getVariable() and + deadLocal(ssa) and + not expectedDead(ssa) and + not overwritten(ssa) and + read(v) and + not def.(AssignExpr).getSource() instanceof NullLiteral and + (def instanceof Assignment or def.(UnaryAssignExpr).getParent() instanceof ExprStmt) +select + def, "This assignment to " + v.getName() + " is useless: the value is never read." diff --git a/java/ql/src/Violations of Best Practice/Dead Code/EmptyFinalize.java b/java/ql/src/Violations of Best Practice/Dead Code/EmptyFinalize.java new file mode 100644 index 00000000000..a13217a306f --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/EmptyFinalize.java @@ -0,0 +1,21 @@ +class ExtendedLog extends Log +{ + // ... + + protected void finalize() { + // BAD: This empty 'finalize' stops 'super.finalize' from being executed. + } +} + +class Log implements Closeable +{ + // ... + + public void close() { + // ... + } + + protected void finalize() { + close(); + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Dead Code/EmptyFinalize.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/EmptyFinalize.qhelp new file mode 100644 index 00000000000..80c32b87b4e --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/EmptyFinalize.qhelp @@ -0,0 +1,38 @@ + + + + + +

    An empty finalize method is useless and may prevent finalization from +working properly. This is because, unlike a constructor, a finalizer does not implicitly call the +finalizer of the superclass. Thus, an empty finalizer prevents any finalization logic that is +defined in any of its superclasses from being executed.

    + +
    + + +

    Do not include an empty finalize method.

    + +
    + + +

    In the following example, the empty finalize method in class ExtendedLog +prevents the finalize method in class Log from being called. The result is +that the log file is not closed. To fix this, remove the empty finalize method.

    + + + +
    + + + +
  • +Java Language Specification: +12.6 Finalization of Class Instances. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Dead Code/EmptyFinalize.ql b/java/ql/src/Violations of Best Practice/Dead Code/EmptyFinalize.ql new file mode 100644 index 00000000000..c200e818af8 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/EmptyFinalize.ql @@ -0,0 +1,21 @@ +/** + * @name Empty body of finalizer + * @description An empty 'finalize' method is useless and prevents its superclass's 'finalize' + * method (if any) from being called. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/empty-finalizer + * @tags reliability + * readability + * external/cwe/cwe-568 + */ +import java + +from FinalizeMethod finalize +where + finalize.fromSource() and + not exists(Stmt s | s.getEnclosingCallable() = finalize | + not s instanceof Block + ) +select finalize, "Empty finalize method." diff --git a/java/ql/src/Violations of Best Practice/Dead Code/FinalizerNullsFields.java b/java/ql/src/Violations of Best Practice/Dead Code/FinalizerNullsFields.java new file mode 100644 index 00000000000..d55d76f835f --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/FinalizerNullsFields.java @@ -0,0 +1,14 @@ +class FinalizedClass { + Object o = new Object(); + String s = "abcdefg"; + Integer i = Integer.valueOf(2); + + @Override + protected void finalize() throws Throwable { + super.finalize(); + //No need to nullify fields + this.o = null; + this.s = null; + this.i = null; + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Dead Code/FinalizerNullsFields.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/FinalizerNullsFields.qhelp new file mode 100644 index 00000000000..c0944725c9b --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/FinalizerNullsFields.qhelp @@ -0,0 +1,95 @@ + + + + + +

    +A finalizer does not need to set an object's fields to null to help the garbage collector. At the point in the Java object life-cycle when the finalize method is +called, the object is no longer reachable from the garbage collection roots. Explicitly setting the object's fields to null does not cause the referenced +objects to be collected by the garbage collector any earlier, and may even adversely affect performance. +

    + +

    +The life-cycle of a Java object has 7 stages: +

    +
      +
    • + Created : Memory is allocated for the object and the initializers and constructors have been run. +
    • +
    • + In use : The object is reachable through a chain of strong references from a garbage collection root. A garbage collection + root is a special class of variable (which includes variables on the stack of any thread, static variables of any class, and + references from Java Native Interface code). +
    • +
    • + Invisible : The object has already gone out of scope, but the stack frame of the method that contained the scope is still + in memory. Not all objects transition into this state. +
    • +
    • + Unreachable : The object is no longer reachable through a chain of strong references. It becomes a candidate for garbage + collection. +
    • +
    • + Collected : The garbage collector has identified that the object can be deallocated. If it has a finalizer, it is marked for + finalization. Otherwise, it is deallocated. +
    • +
    • + Finalized : An object with a finalize method transitions to this state after the finalize method is completed + and the object still remains unreachable. +
    • +
    • + Deallocated : The object is a candidate for deallocation. +
    • +
    + +

    +The call to the finalize method occurs when the object is in the 'Collected' stage. At that point, it is already unreachable from the +garbage collection roots so any of its references to other objects no longer contribute to their reference counts. +

    + +
    + + +

    +Ensure that the finalizer does not contain any null assignments because they are unlikely to help garbage collection. +

    + +

    +If a finalizer does nothing but nullify an object's fields, it is best to completely remove the finalizer. Objects with finalizers +severely affect performance, and you should avoid defining finalize where possible. +

    + +
    + + +

    In the following example, finalize unnecessarily assigns the object's fields to null.

    + + + +
    + + + +
  • + J. Bloch, Effective Java (second edition), Item 7. Addison-Wesley, 2008. +
  • +
  • + IBM developerWorks: + Explicit nulling. +
  • +
  • + Oracle Technology Network: + + How to Handle Java Finalization's Memory-Retention Issues + . +
  • +
  • + S. Wilson and J. Kesselman, Java Platform Performance: Strategies and Tactics, 1st ed., + Appendix A. Prentice Hall, 2001. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Dead Code/FinalizerNullsFields.ql b/java/ql/src/Violations of Best Practice/Dead Code/FinalizerNullsFields.ql new file mode 100644 index 00000000000..35a3b23ac14 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/FinalizerNullsFields.ql @@ -0,0 +1,21 @@ +/** + * @name Finalizer nulls fields + * @description Setting fields to 'null' in a finalizer does not cause the object to be collected + * by the garbage collector any earlier, and may adversely affect performance. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/finalizer-nulls-fields + * @tags efficiency + * maintainability + */ +import java + +from FinalizeMethod m, Assignment assign, FieldAccess lhs, NullLiteral null +where + assign.getEnclosingCallable() = m and + null.getParent() = assign and + lhs = assign.getDest() and + lhs.getField().getDeclaringType() = m.getDeclaringType().getASupertype*() and + m.fromSource() +select assign, "Finalizer nulls fields." diff --git a/java/ql/src/Violations of Best Practice/Dead Code/InterfaceCannotBeImplemented.java b/java/ql/src/Violations of Best Practice/Dead Code/InterfaceCannotBeImplemented.java new file mode 100644 index 00000000000..d5ee2812441 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/InterfaceCannotBeImplemented.java @@ -0,0 +1,9 @@ +interface I { + int clone(); +} + +class C implements I { + public int clone() { + return 23; + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Dead Code/InterfaceCannotBeImplemented.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/InterfaceCannotBeImplemented.qhelp new file mode 100644 index 00000000000..78e13e2f707 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/InterfaceCannotBeImplemented.qhelp @@ -0,0 +1,59 @@ + + + + + +

    +An interface that contains methods whose return types clash with protected +methods on java.lang.Object can never be implemented, because +methods cannot be overloaded based simply on their return type.

    + +
    + + +

    +If the interface is useful, name methods so that they +do not clash with methods in Object. Otherwise you should delete the interface. +

    + +
    + + +

    In the following example, the interface I is useless because the +clone method must return type java.lang.Object:

    + + + +

    +Any attempt to implement the interface produces an error: +

    + +
    +InterfaceCannotBeImplemented.java:6: clone() in C cannot override
    +  clone() in java.lang.Object; attempting to use incompatible return
    +  type
    +found   : int
    +required: java.lang.Object
    +  public int clone() {
    +             ^
    +1 error
    +
    + +
    + + + +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • +
  • +Java Language Specification, Third Edition: +9.2 Interface Members. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Dead Code/InterfaceCannotBeImplemented.ql b/java/ql/src/Violations of Best Practice/Dead Code/InterfaceCannotBeImplemented.ql new file mode 100644 index 00000000000..8c1a1bf4741 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/InterfaceCannotBeImplemented.ql @@ -0,0 +1,29 @@ +/** + * @name Interface cannot be implemented + * @description An interface method that is incompatible with a protected method on + * 'java.lang.Object' means that the interface cannot be implemented. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id java/unimplementable-interface + * @tags maintainability + * useless-code + */ + +import java + +Method protectedObjectMethod(string signature) { + result.getSignature() = signature and + result.isProtected() and + result.getDeclaringType() instanceof TypeObject +} + +from Method method, Method objMethod, Interface impossible +where + method.getDeclaringType() = impossible and + objMethod = protectedObjectMethod(method.getSignature()) and + not hasSubtype*(objMethod.getReturnType(), method.getReturnType()) +select method, + "This method's return type conflicts with Object." + method.getName() + " so $@ can never be implemented.", + impossible, + impossible.getName() diff --git a/java/ql/src/Violations of Best Practice/Dead Code/LocalInitialisedButNotUsed.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/LocalInitialisedButNotUsed.qhelp new file mode 100644 index 00000000000..ef1451768f5 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/LocalInitialisedButNotUsed.qhelp @@ -0,0 +1,21 @@ + + + + + +

    A local variable is initialized, but the variable is never read or written to subsequently. This suggests +that the local variable is either useless and should be removed, or that the value was intended to be used +somewhere. +

    + +
    + +

    Ensure that you check the control and data flow in the method carefully. +If a value is really not needed, consider removing the variable. Be careful, +though: if the right-hand side has a side-effect (like performing a method call), +it is important to keep this to preserve the overall behavior.

    + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Dead Code/LocalInitialisedButNotUsed.ql b/java/ql/src/Violations of Best Practice/Dead Code/LocalInitialisedButNotUsed.ql new file mode 100644 index 00000000000..e4989436d54 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/LocalInitialisedButNotUsed.ql @@ -0,0 +1,21 @@ +/** + * @name Local variable is initialized but not used + * @description A local variable is initialized once, but never read or written to. Either the local variable is useless, or its value was intended to be used but is not. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/unused-initialized-local + * @tags external/cwe/cwe-563 + */ +import java +import DeadLocals + +from LocalVariableDeclExpr ve, LocalVariableDecl v +where + v = ve.getVariable() and + not assigned(v) and + not read(v) and + exists(ve.getInit()) and + not exprHasNoEffect(ve.getInit()) +select v, "Local variable " + v.getName() + " is never read or written to after it is initialised." + diff --git a/java/ql/src/Violations of Best Practice/Dead Code/LocalNotRead.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/LocalNotRead.qhelp new file mode 100644 index 00000000000..ea7474edc4a --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/LocalNotRead.qhelp @@ -0,0 +1,21 @@ + + + + + +

    A local variable is assigned a value, but the variable is never read. This suggests +that the local variable is either useless and should be removed, or that the value was intended to be used +somewhere. +

    + +
    + +

    Ensure that you check the control and data flow in the method carefully. +If a value is really not needed, consider removing the variable. Be careful, +though: if the right-hand side has a side-effect (like performing a method call), +it is important to keep this to preserve the overall behavior.

    + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Dead Code/LocalNotRead.ql b/java/ql/src/Violations of Best Practice/Dead Code/LocalNotRead.ql new file mode 100644 index 00000000000..0ceff67dc6e --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/LocalNotRead.ql @@ -0,0 +1,17 @@ +/** + * @name Local variable is never read + * @description A local variable is written to, but never read. Either the local variable is useless, or its value was intended to be used but is not. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/assigned-local-unread + */ +import java +import DeadLocals + +from LocalScopeVariable v +where + assigned(v) and // Only assignments, not initialization + not read(v) +select v, "Local variable " + v.getName() + " is only assigned to, never read." + diff --git a/java/ql/src/Violations of Best Practice/Dead Code/NonAssignedFields.java b/java/ql/src/Violations of Best Practice/Dead Code/NonAssignedFields.java new file mode 100644 index 00000000000..02c27c6375c --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/NonAssignedFields.java @@ -0,0 +1,16 @@ +class Person { + private String name; + private int age; + + public Person(String name, int age) { + this.age = age; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } +} diff --git a/java/ql/src/Violations of Best Practice/Dead Code/NonAssignedFields.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/NonAssignedFields.qhelp new file mode 100644 index 00000000000..2eb7a2ed61a --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/NonAssignedFields.qhelp @@ -0,0 +1,42 @@ + + + + + +

    It is good practice to initialize every field in a constructor explicitly. A field that is never +assigned any value (except possibly null) just returns +the default value when it is read, or throws a NullPointerException.

    + +
    + + +

    A field whose value is always null (or the +corresponding default value for primitive types, for example 0) is not particularly +useful. Ensure that the code contains an assignment or initialization for each field. To help +satisfy this rule, it is good practice to explicitly initialize every field in the constructor, +even if the default value is acceptable.

    + +

    If the field is genuinely never expected to hold a non-default value, check the statements that +read the field and ensure that they are not making incorrect assumptions about the value of the +field. Consider completely removing the field and rewriting the statements that read it, +as appropriate.

    + +
    + + +

    In the following example, the private field name is not initialized in the constructor +(and thus is implicitly set to null), but there is a getter method to access it.

    + + + +

    Therefore, the following code throws a NullPointerException:

    + +Person p = new Person("Arthur Dent", 30); +int l = p.getName().length(); + +

    To fix the code, name should be initialized in the constructor by adding the +following line: this.name = name;

    +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Dead Code/NonAssignedFields.ql b/java/ql/src/Violations of Best Practice/Dead Code/NonAssignedFields.ql new file mode 100644 index 00000000000..2b674d013f8 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/NonAssignedFields.ql @@ -0,0 +1,90 @@ +/** + * @name Field is never assigned a non-null value + * @description A field that is never assigned a value (except possibly 'null') just returns the + * default value when it is read. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/unassigned-field + * @tags reliability + * maintainability + * useless-code + * external/cwe/cwe-457 + */ +import java +import semmle.code.java.Reflection + +/** + * Holds if `c` is of the form `Class`, where `t` represents `T`. + */ +predicate isClassOf(ParameterizedClass c, RefType t) { + c.getGenericType() instanceof TypeClass and + c.getATypeArgument().getSourceDeclaration() = t.getSourceDeclaration() +} + +/** + * Holds if field `f` is potentially accessed by an `AtomicReferenceFieldUpdater`. + */ +predicate subjectToAtomicReferenceFieldUpdater(Field f) { + exists(Class arfu, Method newUpdater, MethodAccess c | + arfu.hasQualifiedName("java.util.concurrent.atomic", "AtomicReferenceFieldUpdater") and + newUpdater = arfu.getAMethod() and newUpdater.hasName("newUpdater") and + c.getMethod().getSourceDeclaration() = newUpdater and + isClassOf(c.getArgument(0).getType(), f.getDeclaringType()) and + isClassOf(c.getArgument(1).getType(), f.getType()) and + c.getArgument(2).(StringLiteral).getRepresentedString() = f.getName() + ) +} + +/** + * Holds if `f` is ever looked up reflectively. + */ +predicate lookedUpReflectively(Field f) { + exists(MethodAccess getDeclaredField | + isClassOf(getDeclaredField.getQualifier().getType(), f.getDeclaringType()) and + getDeclaredField.getMethod().hasName("getDeclaredField") and + getDeclaredField.getArgument(0).(StringLiteral).getRepresentedString() = f.getName() + ) +} + +/** + * Holds if `rt` registers a VM observer in its static initialiser. + */ +predicate isVMObserver(RefType rt) { + exists(Method register | + register.getDeclaringType().hasQualifiedName("sun.jvm.hotspot.runtime", "VM") and + register.hasName("registerVMInitializedObserver") and + register.getAReference().getEnclosingCallable().(StaticInitializer).getDeclaringType() = rt + ) +} + +from Field f, FieldRead fr +where + f.fromSource() and + fr.getField().getSourceDeclaration() = f and + not f.getDeclaringType() instanceof EnumType and + forall(Assignment ae, Field g | + ae.getDest() = g.getAnAccess() and g.getSourceDeclaration() = f + | + ae.getSource() instanceof NullLiteral + ) and + not exists(UnaryAssignExpr ua, Field g | + ua.getExpr() = g.getAnAccess() and + g.getSourceDeclaration() = f + ) and + not f.isFinal() and + // Exclude fields that may be accessed reflectively. + not reflectivelyWritten(f) and + not lookedUpReflectively(f) and + not subjectToAtomicReferenceFieldUpdater(f) and + // If an object containing `f` is, or may be, passed to a native method, + // assume it initializes the field. + not exists(Callable c | c.isNative() | + c.getAParameter().getType() = f.getDeclaringType() or + c.getAReference().getAnArgument().getType() = f.getDeclaringType() or + c.getDeclaringType() = f.getDeclaringType() + ) and + // Exclude special VM classes. + not isVMObserver(f.getDeclaringType()) +select f, "Field " + f.getName() + " never assigned non-null value, yet it is read at $@.", + fr, fr.getFile().getStem() + ".java:" + fr.getLocation().getStartLine() diff --git a/java/ql/src/Violations of Best Practice/Dead Code/PointlessForwardingMethod.java b/java/ql/src/Violations of Best Practice/Dead Code/PointlessForwardingMethod.java new file mode 100644 index 00000000000..277b87a281b --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/PointlessForwardingMethod.java @@ -0,0 +1,41 @@ +import static java.lang.System.out; + +public class PointlessForwardingMethod { + private static class Bad { + // Violation: This print does nothing but forward to the other one, which is not + // called independently. + public void print(String firstName, String lastName) { + print(firstName + " " + lastName); + } + + public void print(String fullName) { + out.println("Pointless forwarding methods are bad, " + fullName + "..."); + } + } + + private static class Better1 { + // Better: Merge the methods, using local variables to replace the parameters in + // the original version. + public void print(String firstName, String lastName) { + String fullName = firstName + " " + lastName; + out.println("Pointless forwarding methods are bad, " + fullName + "..."); + } + } + + private static class Better2 { + // Better: If there's no complicated logic, you can often remove the extra + // variables entirely. + public void print(String firstName, String lastName) { + out.println( + "Pointless forwarding methods are bad, " + + firstName + " " + lastName + "..." + ); + } + } + + public static void main(String[] args) { + new Bad().print("Foo", "Bar"); + new Better1().print("Foo", "Bar"); + new Better2().print("Foo", "Bar"); + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Dead Code/PointlessForwardingMethod.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/PointlessForwardingMethod.qhelp new file mode 100644 index 00000000000..1047cdbb84e --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/PointlessForwardingMethod.qhelp @@ -0,0 +1,31 @@ + + + +

    +If a class contains two distinct methods of the same name such that:

    +
    1. One method is only ever called from the other method.
    2. +
    3. The calling method calls only the other method and nothing else.
    +

    +Then the first method is no more than a forwarding method for the second and the two methods can +probably be merged.

    + +

    +There are several +advantages to doing this: +

    + +
      +
    • It reduces the cognitive overhead involved in keeping track of the various different overloaded forms of a method.
    • +
    • If both methods are public, it simplifies the API of their containing class, making it more discoverable to other programmers.
    • +
    • It makes it clearer to other programmers that certain methods are called and other methods are not.
    • +
    + +
    + +

    In this example, the two print methods in Bad can be merged, as one is simply a forwarder for the other. The two classes Better1 and Better2 +show two alternative ways of merging the methods.

    + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Dead Code/PointlessForwardingMethod.ql b/java/ql/src/Violations of Best Practice/Dead Code/PointlessForwardingMethod.ql new file mode 100644 index 00000000000..31e051cd77b --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/PointlessForwardingMethod.ql @@ -0,0 +1,39 @@ +/** + * @name Pointless forwarding method + * @description A method forwards calls to another method of the same name that is not called independently. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/useless-forwarding-method + * @tags maintainability + */ + +import java + +predicate ignored(Method m) { + m.isAbstract() or + m.overrides(_) +} + +Method forwarderCandidate(Method forwardee) { + result != forwardee and + result.getName() = forwardee.getName() and + result.getDeclaringType() = forwardee.getDeclaringType() and + forex(MethodAccess c | c.getMethod() = forwardee | c.getCaller() = result) and + forall(MethodAccess c | c.getCaller() = result | c.getMethod() = forwardee) +} + +from Method forwarder, Method forwardee +where + forwarder = forwarderCandidate(forwardee) and + // Exclusions + not ignored(forwarder) and + not ignored(forwardee) and + not exists(VirtualMethodAccess c | + c.getMethod() = forwardee and + c.getCaller() = forwarder and + c.(MethodAccess).hasQualifier() + ) +select forwarder.getSourceDeclaration(), + "This method is a forwarder for $@, which is not called independently - the methods can be merged.", + forwardee.getSourceDeclaration(), forwardee.getName() diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.java b/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.java new file mode 100644 index 00000000000..ea1f4edc881 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.java @@ -0,0 +1,25 @@ +// Version containing unread local variable +public class Cart { + private Map items = ...; + public void add(Item i) { + Integer quantity = items.get(i); + if (quantity = null) + quantity = 1; + else + quantity++; + Integer oldQuantity = items.put(i, quantity); // AVOID: Unread local variable + } +} + +// Version with unread local variable removed +public class Cart { + private Map items = ...; + public void add(Item i) { + Integer quantity = items.get(i); + if (quantity = null) + quantity = 1; + else + quantity++; + items.put(i, quantity); + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.qhelp new file mode 100644 index 00000000000..fea0b602a71 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.qhelp @@ -0,0 +1,42 @@ + + + + + +

    A local variable that is never read is useless.

    + +

    As a matter of good practice, there should be no unused or useless code. It makes the program +more difficult to understand and maintain, and can waste a programmer's time.

    + +
    + + +

    This rule applies to variables that are never used as well as variables that are only +written to but never read. In both cases, ensure that no operations are missing that would use the +local variable. If appropriate, simply remove the declaration. However, if the variable is +written to, ensure that any side-effects in the assignments are retained. (For further +details, see the example.)

    + +
    + + +

    In the following example, the local variable oldQuantity is assigned a value but +never read. In the fixed version of the example, the variable is removed but the call to +items.put in the assignment is retained.

    + + + +
    + + + +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.ql b/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.ql new file mode 100644 index 00000000000..c2052344b7b --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.ql @@ -0,0 +1,30 @@ +/** + * @name Unread local variable + * @description A local variable that is never read is redundant. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/local-variable-is-never-read + * @tags maintainability + * useless-code + * external/cwe/cwe-561 + */ + +import java + +VarAccess getARead(LocalVariableDecl v) { + v.getAnAccess() = result and + not exists(Assignment assign | assign.getDest() = result) +} + +predicate readImplicitly(LocalVariableDecl v) { + exists(TryStmt t | t.getAResourceDecl().getAVariable() = v.getDeclExpr()) +} + +from LocalVariableDecl v +where + not exists(getARead(v)) and + // Discarded exceptions are covered by another query. + not exists(CatchClause cc | cc.getVariable().getVariable() = v) and + not readImplicitly(v) +select v, "Variable '" + v + "' is never read." diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnusedField.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/UnusedField.qhelp new file mode 100644 index 00000000000..eec006ec399 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/UnusedField.qhelp @@ -0,0 +1,35 @@ + + + + + +

    A field that is neither public nor protected and never accessed +is typically a leftover from old refactorings or a sign of incomplete or pending +code changes.

    + +

    This rule does not apply to a field in a serializable class because it may be accessed during +serialization and deserialization.

    + +

    Fields annotated with @SuppressWarnings("unused") are also not reported.

    + +
    + + +

    If an unused field is a leftover from old refactorings, you should just remove it. If it indicates +incomplete or pending code changes, finish making the changes and remove the field if it is not +needed.

    + +
    + + + +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnusedField.ql b/java/ql/src/Violations of Best Practice/Dead Code/UnusedField.ql new file mode 100644 index 00000000000..114beaaabc8 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/UnusedField.ql @@ -0,0 +1,30 @@ +/** + * @name Unused field + * @description A field that is never used is probably unnecessary. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/unused-field + * @tags maintainability + * useless-code + * external/cwe/cwe-561 + */ +import java +import semmle.code.java.Reflection +import semmle.code.java.frameworks.Lombok + +from Field f +where + not (f.isPublic() or f.isProtected()) and + f.fromSource() and + not f.getDeclaringType() instanceof EnumType and + not exists(VarAccess va | va.getVariable().(Field).getSourceDeclaration() = f) and + // Exclude results in generated classes. + not f.getDeclaringType() instanceof GeneratedClass and + // Exclude fields that may be reflectively read (this includes standard serialization). + not reflectivelyRead(f) and + // Exclude fields with deliberately suppressed warnings. + not f.suppressesWarningsAbout("unused") and + // Exclude fields with relevant Lombok annotations. + not f instanceof LombokGetterAnnotatedField +select f, "Unused field " + f.getName() + " in " + f.getDeclaringType().getName() + "." diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnusedLabel.java b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLabel.java new file mode 100644 index 00000000000..03b6c701820 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLabel.java @@ -0,0 +1,16 @@ +public class WebStore { + public boolean itemIsBeingBought(Item item) { + boolean found = false; + carts: // AVOID: Unused label + for (int i = 0; i < carts.size(); i++) { + Cart cart = carts.get(i); + for (int j = 0; j < cart.numItems(); j++) { + if (item.equals(cart.getItem(j))) { + found = true; + break; + } + } + } + return found; + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnusedLabel.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLabel.qhelp new file mode 100644 index 00000000000..ca936ceff8f --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLabel.qhelp @@ -0,0 +1,49 @@ + + + + + +

    Loop and switch statements can be labeled. These labels +can serve as targets for break or +continue statements, to specify which loop or switch statement they refer to.

    + +

    Apart from serving as such jump targets, the labels have no effect on +program behavior, which means that having an unused label is suspicious.

    + +
    + + +

    If the label is used to document the intended behavior of a loop or switch statement, remove it. +It is better to use comments for this purpose. However, an unused label may +indicate that something is wrong: that some of the nested break +or continue statements should be using the label. In this case, the current control +flow is probably wrong, and you should adjust some jumps to use the label +after checking the desired behavior.

    + +
    + + +

    The following example uses a loop and a nested loop to check whether any of the +currently active shopping carts contains a particular item. On line 4, the carts: label +is unused. Inspecting the code, we can see that the break statement on line 10 +is inefficient because it only breaks out of the nested loop. It +could in fact break out of the outer loop, which should improve +performance in common cases. By changing the statement on line 10 to read break carts;, the label +is no longer unused and we improve the code.

    + + + +
    + + + +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnusedLabel.ql b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLabel.ql new file mode 100644 index 00000000000..cc453f1334e --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLabel.ql @@ -0,0 +1,18 @@ +/** + * @name Unused label + * @description An unused label for a loop or 'switch' statement is either redundant or indicates + * incorrect 'break' or 'continue' statements. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/unused-label + * @tags maintainability + * useless-code + * external/cwe/cwe-561 + */ + +import java + +from LabeledStmt label +where not exists(JumpStmt jump | jump.getTargetLabel() = label) +select label, "Label '" + label.getLabel() + "' is not used." diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.qhelp new file mode 100644 index 00000000000..b870eb8463e --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.qhelp @@ -0,0 +1,30 @@ + + + + + +

    A local variable that is never accessed nor initialized +is typically a leftover from old refactorings or a sign of incomplete or pending +code changes.

    + +
    + + +

    If an unused variable is a leftover from old refactorings, you should just remove it. If it indicates +incomplete or pending code changes, finish making the changes and remove the variable if it is not +needed.

    + +
    + + + +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.ql b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.ql new file mode 100644 index 00000000000..482d5501d81 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.ql @@ -0,0 +1,31 @@ +/** + * @name Unused local variable + * @description A local variable is entirely unused: it is not initialized, written to or read. The variable serves no purpose and obscures the code. It should be removed. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/unused-local-variable + * @tags external/cwe/cwe-563 + */ +import java +import DeadLocals + +predicate exceptionVariable(LocalVariableDeclExpr ve) { + exists(CatchClause catch | catch.getVariable() = ve) +} + +predicate enhancedForVariable(LocalVariableDeclExpr ve) { + exists(EnhancedForStmt for | for.getVariable() = ve) +} + +from LocalVariableDeclExpr ve, LocalVariableDecl v +where + v = ve.getVariable() and + not assigned(v) and + not read(v) and + (not exists(ve.getInit()) or exprHasNoEffect(ve.getInit())) and + // Remove contexts where Java forces a variable declaration: enhanced-for and catch clauses. + // Rules about catch clauses belong in an exception handling query + not exceptionVariable(ve) and + not enhancedForVariable(ve) +select v, "Unused local variable " + v.getName() + ". The variable is never read or written to and should be removed." diff --git a/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.java b/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.java new file mode 100644 index 00000000000..2618ccc9a7f --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.java @@ -0,0 +1,25 @@ +class Server +{ + public void respond(Event event) + { + Message reply = null; + switch (event) { + case PING: + reply = Message.PONG; + // Missing 'break' statement + case TIMEOUT: + reply = Message.PING; + case PONG: + // No reply needed + } + if (reply != null) + send(reply); + } + + private void send(Message message) { + // ... + } +} + +enum Event { PING, PONG, TIMEOUT } +enum Message { PING, PONG } \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.qhelp b/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.qhelp new file mode 100644 index 00000000000..688cf872be3 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.qhelp @@ -0,0 +1,54 @@ + + + + + +

    In a switch statement, execution 'falls through' from one case to +the next, unless the case ends with a break statement. A +common programming error is to forget to insert a break at the end +of a case.

    + +
    + + +

    End each case with a break statement or, if execution is +supposed to fall through to the next case, comment the last line of the +case with the following comment: /* falls through */

    + +

    Such comments are not required for a completely empty case that is +supposed to share the same implementation with the subsequent case.

    + +
    + + +

    In the following example, the PING case is missing a +break statement. As a result, after reply is assigned the value of +Message.PONG, execution falls through to the TIMEOUT case. Then the value +of reply is erroneously assigned the value of Message.PING. To fix this, +insert break; at the end of the PING case.

    + + + + +
    + + + +
  • + J. Bloch and N. Gafter, Java Puzzlers: Traps, Pitfalls, and Corner Cases, Puzzle 23. + Addison-Wesley, 2005. +
  • +
  • +Code Conventions for the Java Programming Language: +7.8 switch Statements. +
  • +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.ql b/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.ql new file mode 100644 index 00000000000..68d4e1c23f1 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.ql @@ -0,0 +1,23 @@ +/** + * @name Unterminated switch case + * @description A 'case' statement that does not contain a 'break' statement allows execution to + * 'fall through' to the next 'case', which may not be intended. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/switch-fall-through + * @tags reliability + * readability + * external/cwe/cwe-484 + */ +import java +import Common + +from SwitchStmt s, Stmt c +where + c = s.getACase() and + not c.(ControlFlowNode).getASuccessor() instanceof ConstCase and + not c.(ControlFlowNode).getASuccessor() instanceof DefaultCase and + not s.(Annotatable).suppressesWarningsAbout("fallthrough") and + mayDropThroughWithoutComment(s, c) +select c, "Switch case may fall through to the next case. Use a break or return to terminate this case." diff --git a/java/ql/src/Violations of Best Practice/Declarations/Common.qll b/java/ql/src/Violations of Best Practice/Declarations/Common.qll new file mode 100644 index 00000000000..0ad18d598d6 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Declarations/Common.qll @@ -0,0 +1,68 @@ +import java + +private Stmt getASwitchChild(SwitchStmt s) { + result = s.getAChild() or + exists(Stmt mid | mid = getASwitchChild(s) and not mid instanceof SwitchStmt and result = mid.getAChild()) +} + +private predicate blockInSwitch(SwitchStmt s, BasicBlock b) { + b.getFirstNode().getEnclosingStmt() = getASwitchChild(s) +} + +private predicate switchCaseControlFlow(SwitchStmt switch, BasicBlock b1, BasicBlock b2) { + blockInSwitch(switch, b1) and + b1.getABBSuccessor() = b2 and + blockInSwitch(switch, b2) +} + +predicate switchCaseControlFlowPlus(SwitchStmt switch, BasicBlock b1, BasicBlock b2) { + switchCaseControlFlow(switch, b1, b2) or + exists(BasicBlock mid | + switchCaseControlFlowPlus(switch, mid, b2) and + switchCaseControlFlow(switch, b1, mid) and + not mid.getFirstNode() = switch.getACase() + ) +} + +predicate mayDropThroughWithoutComment(SwitchStmt switch, Stmt switchCase) { + switchCase = switch.getACase() and + exists(Stmt other, BasicBlock b1, BasicBlock b2 | + b1.getFirstNode() = switchCase and + b2.getFirstNode() = other and + switchCaseControlFlowPlus(switch, b1, b2) and + other = switch.getACase() and + not fallThroughCommented(other) + ) +} + +private +predicate fallThroughCommented(Stmt case) { + exists(Location loc | + loc = case.getLocation() and + loc.getStartLine() = fallThroughCommentedLine(loc.getFile()) + ) +} + +private +int fallThroughCommentedLine(File f) { + exists(Location loc, JavadocText text | + loc.getFile() = f and + text.getLocation() = loc and + text.getText().toLowerCase().regexpMatch(".*falls?[ -]?(through|thru).*") and + result = loc.getStartLine() + 1 + ) or exists(int mid | + mid = fallThroughCommentedLine(f) and + not stmtLine(f) = mid and + mid < max(stmtLine(f)) and + result = mid + 1 + ) +} + +private +int stmtLine(File f) { + exists(Stmt s, Location loc | + s.getLocation() = loc and + loc.getFile() = f and + loc.getStartLine() = result + ) +} diff --git a/java/ql/src/Violations of Best Practice/Declarations/MakeImportsExplicit.java b/java/ql/src/Violations of Best Practice/Declarations/MakeImportsExplicit.java new file mode 100644 index 00000000000..82b6cebdc97 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Declarations/MakeImportsExplicit.java @@ -0,0 +1,8 @@ +import java.util.*; // AVOID: Implicit import statements +import java.awt.*; + +public class Customers { + public List getCustomers() { // Compiler error: 'List' is ambiguous. + ... + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Declarations/MakeImportsExplicit.qhelp b/java/ql/src/Violations of Best Practice/Declarations/MakeImportsExplicit.qhelp new file mode 100644 index 00000000000..383f6f7a706 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Declarations/MakeImportsExplicit.qhelp @@ -0,0 +1,63 @@ + + + + + +

    Imports can be categorized as explicit (for example +import java.util.List;) or implicit (also known as +'on-demand', for example import java.util.*;):

    + +
      +
    • Implicit imports give access to all visible types in the type (or package) that precedes +the ".*"; types imported in this way never shadow other types.
    • +
    • Explicit imports give access to just the named type; they can shadow other types +that would normally be visible through an implicit import, or through the +normal package visibility rules.
    • +
    + +

    It is often considered bad practice to use implicit imports. The only +advantage to doing so is making the code more concise, and there are a number +of disadvantages:

    + +
      +
    • The exact dependencies of a file are not visible at a glance.
    • +
    • Confusing compile-time errors can be introduced if a type name +is used that could originate from several implicit imports.
    • +
    + +
    + + +

    For readability, it is recommended to use explicit imports instead of implicit imports. +Many modern IDEs provide automatic functionality to help achieve this, typically under the name +"Organize imports". They can also fold away the import declarations, and automatically +manage imports: adding them when a particular type is auto-completed by the editor, and +removing them when they are not necessary. This functionality makes implicit imports mainly +redundant.

    + +
    + + +

    The following example uses implicit imports. This means that it is not clear to a programmer +where the List type on line 5 is imported from.

    + + + +

    To improve readability, the implicit imports should be replaced by explicit imports. For example, +import java.util.*; should be replaced by import java.util.List; on line 1.

    + +
    + + + + + +
  • Java Language Specification: + 6.4.1 Shadowing, + 7.5.2 Type-Import-on-Demand Declarations.
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Declarations/MakeImportsExplicit.ql b/java/ql/src/Violations of Best Practice/Declarations/MakeImportsExplicit.ql new file mode 100644 index 00000000000..5137c48a696 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Declarations/MakeImportsExplicit.ql @@ -0,0 +1,14 @@ +/** + * @name Implicit import + * @description An implicit import obscures the dependencies of a file and may cause confusing + * compile-time errors. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/implicit-import + * @tags maintainability + */ +import java + +from ImportOnDemandFromPackage i +select i, "It is advisable to make imports explicit." diff --git a/java/ql/src/Violations of Best Practice/Declarations/NoConstantsOnly.java b/java/ql/src/Violations of Best Practice/Declarations/NoConstantsOnly.java new file mode 100644 index 00000000000..2ce4f3067de --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Declarations/NoConstantsOnly.java @@ -0,0 +1,15 @@ +public class NoConstantsOnly { + static interface MathConstants + { + public static final Double Pi = 3.14; + } + + static class Circle implements MathConstants + { + public double radius; + public double area() + { + return Math.pow(radius, 2) * Pi; + } + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Declarations/NoConstantsOnly.qhelp b/java/ql/src/Violations of Best Practice/Declarations/NoConstantsOnly.qhelp new file mode 100644 index 00000000000..05b5db1c9ac --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Declarations/NoConstantsOnly.qhelp @@ -0,0 +1,65 @@ + + + + + +

    Definitions of constants (meaning static, final fields) should be +placed in an appropriate class where they belong logically. + +However, it is usually bad practice to implement an interface (or extend an abstract class) +only to put a number of constant definitions into scope.

    + +
    + + +

    +The preferred way of putting the constant definitions into scope is to use the +import static directive, which allows a compilation unit +to put any visible static members from other classes into scope. +

    + +

    This issue is discussed in [Bloch]:

    + +

    +That a class uses some constants internally is an implementation detail. +Implementing a constant interface causes this implementation detail to +leak into the classes exported API. It is of no consequence to the +users of a class that the class implements a constant interface. In +fact, it may even confuse them. Worse, it represents a commitment: if +in a future release the class is modified so that it no longer needs +to use the constants, it still must implement the interface to ensure +binary compatibility. +

    +
    + +

    To prevent this pollution of a class's binary interface, it +is best to move the constant definitions to whatever concrete class +uses them most frequently. Users of the definitions could use import static +to access the relevant fields.

    + +
    + + +

    In the following example, the interface MathConstants has been defined only to hold +a constant.

    + + + +

    Instead, the constant should be moved to the Circle class or another class +that uses the constant frequently.

    + +
    + + + +
  • +J. Bloch, Effective Java (second edition), +Item 19. +Addison-Wesley, 2008. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Declarations/NoConstantsOnly.ql b/java/ql/src/Violations of Best Practice/Declarations/NoConstantsOnly.ql new file mode 100644 index 00000000000..534c0eb3670 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Declarations/NoConstantsOnly.ql @@ -0,0 +1,54 @@ +/** + * @name Constant interface anti-pattern + * @description Implementing an interface (or extending an abstract class) + * only to put a number of constant definitions into scope is considered bad practice. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/constants-only-interface + * @tags maintainability + * modularity + */ + +import semmle.code.java.Member + +class ConstantField extends Field { + ConstantField() { + this.isStatic() and this.isFinal() + } +} + +pragma[noinline] +predicate typeWithConstantField(RefType t) { + exists(ConstantField f | f.getDeclaringType() = t) +} + +class ConstantRefType extends RefType { + ConstantRefType() { + fromSource() and + ( + this instanceof Interface or + this instanceof Class and this.isAbstract() + ) and + typeWithConstantField(this) and + forall(Member m | m.getDeclaringType() = this | + m.(Constructor).isDefaultConstructor() or + m instanceof StaticInitializer or + m instanceof ConstantField + ) + } + + string getKind() { + result = "interface" and this instanceof Interface or + result = "class" and this instanceof Class + } +} + +from ConstantRefType t, RefType sub +where + sub.fromSource() and + sub.getASupertype() = t and + not sub instanceof ConstantRefType and + sub = sub.getSourceDeclaration() +select sub, "Type " + sub.getName() + " implements constant " + t.getKind() + " $@.", + t, t.getName() diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions-comment.java b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions-comment.java new file mode 100644 index 00000000000..a71288df47d --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions-comment.java @@ -0,0 +1,9 @@ +synchronized void waitIfAutoSyncScheduled() { + try { + while (isAutoSyncScheduled) { + this.wait(1000); + } + } catch (InterruptedException e) { + // Expected exception. The file cannot be synchronized yet. + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions-good.java b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions-good.java new file mode 100644 index 00000000000..0085f15c2db --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions-good.java @@ -0,0 +1,10 @@ +// Exception is passed to 'ignore' method with a comment +synchronized void waitIfAutoSyncScheduled() { + try { + while (isAutoSyncScheduled) { + this.wait(1000); + } + } catch (InterruptedException e) { + Exceptions.ignore(e, "Expected exception. The file cannot be synchronized yet."); + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions-ignore.java b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions-ignore.java new file mode 100644 index 00000000000..6985620e530 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions-ignore.java @@ -0,0 +1,5 @@ +// 'ignore' method. This method does nothing, but can be called +// to document the reason why the exception can be ignored. +public static void ignore(Throwable e, String message) { + +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions.java b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions.java new file mode 100644 index 00000000000..d41a6105e17 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions.java @@ -0,0 +1,10 @@ +// Dropped exception, with no information on whether +// the exception is expected or not +synchronized void waitIfAutoSyncScheduled() { + try { + while (isAutoSyncScheduled) { + this.wait(1000); + } + } catch (InterruptedException e) { + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions.qhelp b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions.qhelp new file mode 100644 index 00000000000..d8695826512 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions.qhelp @@ -0,0 +1,62 @@ + + + + + +

    You should not drop an exception, because it +indicates that an unusual program state has been reached. +This usually requires corrective actions to be performed to recover +from the exceptional state and try to resume normal program operation. +

    + +
    + + +

    +You should do one of the following:

    + +
      +
    • Catch and handle the exception.
    • +
    • Throw the exception to the outermost level of nesting.
    • +
    + +

    Note that usually you should catch and handle a checked exception, but you can throw an +unchecked exception to the outermost level. +

    + +

    There is occasionally a valid reason for ignoring an exception. In such cases, +you should document the reason to improve the readability of the code. Alternatively, +you can implement a static method with an empty body to handle these exceptions. +Instead of dropping the exception altogether, you can then pass it to the static method with +a string explaining the reason for ignoring it.

    + +
    +
    +

    The following example shows a dropped exception.

    + + +

    The following example adds a comment to document why the exception may be ignored.

    + + +

    The following example shows how you can improve code readability by defining a new utility method.

    + + +

    The following example shows the exception being passed to ignore with a comment.

    + + +
    + + + +
  • + J. Bloch, Effective Java (second edition), Item 65. Addison-Wesley, 2008. +
  • +
  • + The Java Tutorials: Unchecked Exceptions - The Controversy. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions.ql b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions.ql new file mode 100644 index 00000000000..8a0fa7f02b8 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions.ql @@ -0,0 +1,21 @@ +/** + * @name Discarded exception + * @description Dropping an exception may allow an unusual program state to continue + * without recovery. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/discarded-exception + * @tags reliability + * correctness + * exceptions + * external/cwe/cwe-391 + */ +import java + +from CatchClause cc +where + not exists(cc.getBlock().getAStmt()) and + not cc.getBlock().getNumberOfCommentLines() > 0 and + not cc.getEnclosingCallable().getDeclaringType() instanceof TestClass +select cc, "Exceptions should not be dropped." diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/ExceptionCatch.java b/java/ql/src/Violations of Best Practice/Exception Handling/ExceptionCatch.java new file mode 100644 index 00000000000..a12a44dc2e1 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Exception Handling/ExceptionCatch.java @@ -0,0 +1,13 @@ +FileInputStream fis = ... +try { + fis.read(); +} catch (Throwable e) { // BAD: The exception is too general. + // Handle this exception +} + +FileInputStream fis = ... +try { + fis.read(); +} catch (IOException e) { // GOOD: The exception is specific. + // Handle this exception +} diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/ExceptionCatch.qhelp b/java/ql/src/Violations of Best Practice/Exception Handling/ExceptionCatch.qhelp new file mode 100644 index 00000000000..c5340797052 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Exception Handling/ExceptionCatch.qhelp @@ -0,0 +1,51 @@ + + + + + +

    Catching Throwable or Exception is dangerous +because these can include an Error such as OutOfMemoryError +or a RuntimeException such as ArrayIndexOutOfBoundsException. +These should normally be propagated to the outermost level because they generally +indicate a program state from which normal operation cannot be recovered.

    + +
    + + +

    It is usually best to ensure that exceptions that are caught in a catch clause +are as specific as possible to avoid inadvertently suppressing more serious problems.

    + +
    + + +

    In the following example, the catch clause in the first try block +catches Throwable. However, when performing read operations on a +FileInputStream within a try block, the corresponding catch +clause should normally catch IOException instead. This is shown in the second, modified +try block. +

    + + + +
    + + + +
  • + J. Bloch and N. Gafter, Java Puzzlers: Traps, Pitfalls, and Corner Cases, + Puzzle 44. + Addison-Wesley, 2005. +
  • +
  • + Java Platform, Standard Edition 6, API Specification: + Throwable, + Error, + Exception, + RuntimeException. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/ExceptionCatch.ql b/java/ql/src/Violations of Best Practice/Exception Handling/ExceptionCatch.ql new file mode 100644 index 00000000000..6d2c7fc776d --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Exception Handling/ExceptionCatch.ql @@ -0,0 +1,59 @@ +/** + * @name Overly-general catch clause + * @description Catching 'Throwable' or 'Exception' is dangerous because these can include + * 'Error' or 'RuntimeException'. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/overly-general-catch + * @tags reliability + * external/cwe/cwe-396 + */ +import java + +private +predicate relevantTypeNames(string typeName, string message) { + // `Throwable` is the more severe case due to `Error`s such as `OutOfMemoryError`. + typeName = "Throwable" and message = "Error" + or + // `Exception` includes `RuntimeException`s such as `ArrayIndexOutOfBoundsException`. + typeName = "Exception" and message = "RuntimeException" +} + +private +Type getAThrownExceptionType(TryStmt t) { + exists(MethodAccess ma, Exception e | + t.getBlock() = ma.getEnclosingStmt().getParent*() and + ma.getMethod().getAnException() = e and + result = e.getType() + ) or + exists(ClassInstanceExpr cie, Exception e | + t.getBlock() = cie.getEnclosingStmt().getParent*() and + cie.getConstructor().getAnException() = e and + result = e.getType() + ) or + exists(ThrowStmt ts | + t.getBlock() = ts.getParent*() and + result = ts.getExpr().getType() + ) +} + +from CatchClause cc, LocalVariableDeclExpr v, TryStmt t, string typeName, string message +where + relevantTypeNames(typeName, message) and + t.getACatchClause() = cc and + cc.getVariable() = v and + v.getType().(RefType).hasQualifiedName("java.lang", typeName) and + // It's usually OK if the exception is logged in some way, or re-thrown. + not exists(v.getAnAccess()) and + // Exclude results in test code. + not cc.getEnclosingCallable().getDeclaringType() instanceof TestClass and + // Check that all exceptions thrown in the try block are + // either more specific than the caught type or unrelated to it. + not exists(Type et | et = getAThrownExceptionType(t) | + et.(RefType).getASubtype*().hasQualifiedName("java.lang", typeName) + ) +select + cc, + "Do not catch '" + cc.getVariable().getType() + "'" + + "; " + message + "s should normally be propagated." diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.java b/java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.java new file mode 100644 index 00000000000..edcace8594f --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.java @@ -0,0 +1,3 @@ +java.io.InputStream is = (...); +byte[] b = new byte[16]; +is.read(b); \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.qhelp b/java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.qhelp new file mode 100644 index 00000000000..fcfcb5414cc --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.qhelp @@ -0,0 +1,88 @@ + + + + + +

    Many methods in the Java Development Kit (for examples, see the references below) +return status values (for example, as an int) to indicate +whether the method execution finished normally. They may return an +error code if the method did not finish normally. +If the method result is not checked, exceptional method executions +may cause subsequent code to fail. +

    + +
    + + +

    You should insert additional code to check the return value and take appropriate action.

    + +
    + + +

    +The following example uses the java.io.InputStream.read method to +read 16 bytes from an input stream and store them in an array. +However, read may not actually be able to read as many +bytes as requested, for example because the stream is exhausted. +Therefore, the code should not simply rely on the array b being filled with +precisely 16 bytes from the input stream. +Instead, the code should check the method's return value, which indicates the number of bytes actually read.

    + + + +
    + + + +
  • + CERT Secure Coding Standards: + EXP00-J. Do not ignore values returned by methods. +
  • +
  • + Java API Documentation, java.util.Queue: + offer. +
  • +
  • + Java API Documentation, java.util.concurrent.BlockingQueue: + offer. +
  • +
  • + Java API Documentation, java.util.concurrent.locks.Condition: + await, + + awaitUntil, + + awaitNanos. +
  • +
  • + Java API Documentation, java.io.File: + createNewFile, + + delete, + + mkdir, + + renameTo, + + setLastModified, + + setReadOnly, + + setWritable(boolean), + + setWritable(boolean, boolean). +
  • +
  • + Java API Documentation, java.io.InputStream: + skip, + + read(byte[]), + + read(byte[], int, int). +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.ql b/java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.ql new file mode 100644 index 00000000000..b1c36234f68 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.ql @@ -0,0 +1,60 @@ +/** + * @name Ignored error status of call + * @description Ignoring an exceptional value that is returned by a method may cause subsequent + * code to fail. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/ignored-error-status-of-call + * @tags reliability + * correctness + * external/cwe/cwe-391 + */ +import java + +class SpecialMethod extends Method { + predicate isMethod(string pack, string clss, string name, int numparam) { + this.hasName(name) and + this.getNumberOfParameters() = numparam and + this.getDeclaringType().getASupertype*().getSourceDeclaration().hasQualifiedName(pack, clss) + } +} + +predicate unboundedQueue(RefType t) { + exists(string pack, string clss | + t.getASupertype*().getSourceDeclaration().hasQualifiedName(pack, clss) + | + pack = "java.util" and clss = "ArrayDeque" or + pack = "java.util" and clss = "LinkedList" or + pack = "java.util" and clss = "PriorityQueue" or + pack = "java.util.concurrent" and clss = "ConcurrentLinkedQueue" or + pack = "java.util.concurrent" and clss = "ConcurrentLinkedDeque" or + pack = "java.util.concurrent" and clss = "DelayQueue" or + pack = "java.util.concurrent" and clss = "LinkedTransferQueue" or + pack = "java.util.concurrent" and clss = "PriorityBlockingQueue" + ) +} + +from MethodAccess ma, SpecialMethod m +where + ma.getParent() instanceof ExprStmt and + m = ma.getMethod() and + ( + m.isMethod("java.util", "Queue", "offer", 1) and not unboundedQueue(m.getDeclaringType()) or + m.isMethod("java.util.concurrent", "BlockingQueue", "offer", 3) and not unboundedQueue(m.getDeclaringType()) or + m.isMethod("java.util.concurrent.locks", "Condition", "await", 2) or + m.isMethod("java.util.concurrent.locks", "Condition", "awaitUntil", 1) or + m.isMethod("java.util.concurrent.locks", "Condition", "awaitNanos", 1) or + m.isMethod("java.io", "File", "createNewFile", 0) or + m.isMethod("java.io", "File", "mkdir", 0) or + m.isMethod("java.io", "File", "renameTo", 1) or + m.isMethod("java.io", "File", "setLastModified", 1) or + m.isMethod("java.io", "File", "setReadOnly", 0) or + m.isMethod("java.io", "File", "setWritable", 1) or + m.isMethod("java.io", "File", "setWritable", 2) or + m.isMethod("java.io", "InputStream", "skip", 1) or + m.isMethod("java.io", "InputStream", "read", 1) or + m.isMethod("java.io", "InputStream", "read", 3) + ) +select ma, "Method " + ma.getEnclosingCallable().getName() + " ignores exceptional return value of " + + ma.getMethod().getDeclaringType().getName() + "." + ma.getMethod().getName() + "." diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.java b/java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.java new file mode 100644 index 00000000000..01a43f27077 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.java @@ -0,0 +1,12 @@ +Customer getNext(List queue) { + if (queue == null) + return null; + LinkedList myQueue = (LinkedList)queue; // AVOID: Cast to concrete type. + return myQueue.poll(); +} + +Customer getNext(Queue queue) { + if (queue == null) + return null; + return queue.poll(); // GOOD: Use abstract type. +} diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.qhelp b/java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.qhelp new file mode 100644 index 00000000000..2e09bd7cc4a --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.qhelp @@ -0,0 +1,59 @@ + + + + + +

    Most collections in the Java standard library are defined by an abstract interface +(for example java.util.List or java.util.Set), which is +implemented by a range of concrete classes and a range of wrappers. Normally, except +when constructing an object, it is better to use the abstract types because this avoids +assumptions about what the implementation is.

    + +

    A cast from an abstract to a concrete collection +makes the code brittle by ensuring it works only for one possible +implementation class and not others. Usually, such casts are +either an indication of over-reliance on concrete implementation types, or of the +fact that the wrong abstract type was used.

    + +
    + + +

    It is usually best to use the abstract type consistently in variable, field and parameter +declarations.

    + +

    There may be individual exceptions. For example, it is common to declare variables +as LinkedHashSet rather than Set when the iteration order +matters and only the LinkedHashSet implementation provides the right +behavior.

    + +
    + + +

    The following example illustrates a situation where the wrong abstract type is used. +The List interface does not provide a poll method, so +the original code casts queue down to the concrete type LinkedList, which +does. To avoid this downcasting, simply use the correct abstract type for this method, namely +Queue. This documents the intent of the programmer and allows for various implementations +of queues to be used by clients of this method.

    + + + +
    + + + +
  • + J. Bloch, Effective Java (second edition), + Item 52. + Addison-Wesley, 2008. +
  • +
  • + Java 6 API Specification: + Collection. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.ql b/java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.ql new file mode 100644 index 00000000000..009e0800dfa --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.ql @@ -0,0 +1,60 @@ +/** + * @name Cast from abstract to concrete collection + * @description A cast from an abstract collection to a concrete implementation type makes the + * code brittle. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id java/abstract-to-concrete-cast + * @tags reliability + * maintainability + * modularity + * external/cwe/cwe-485 + */ +import java +import semmle.code.java.Collections + +predicate guardedByInstanceOf(VarAccess e, RefType t) { + exists(IfStmt s, InstanceOfExpr instanceCheck, Type checkType | + s.getCondition() = instanceCheck + and + instanceCheck.getTypeName().getType() = checkType + and + // The same variable appears as the subject of the `instanceof`. + instanceCheck.getExpr() = e.getVariable().getAnAccess() + and + // The checked type is either the type itself, or a raw version. For example, it is usually + // fine to check for `x instanceof ArrayList` and then cast to `ArrayList`, because + // the generic parameter is usually known. + (checkType = t or checkType = t.getSourceDeclaration().(GenericType).getRawType()) + and + // The expression appears in one of the branches. + // (We do not verify here whether the guard is correctly implemented.) + exists(Stmt branch | branch = s.getThen() or branch = s.getElse() | + branch = e.getEnclosingStmt().getParent+() + ) + ) +} + +from CastExpr e, CollectionType c, CollectionType coll, string abstractName, string concreteName +where + coll instanceof Interface and + c instanceof Class and + // The result of the cast has type `c`. + e.getType() = c and + // The expression inside the cast has type `coll`. + e.getExpr().getType() = coll and + // The cast does not occur inside a check that the variable has that type. + // In this case there is not really a break of abstraction, since it is not + // *assumed* that the variable has that type. In practice, this usually corresponds + // to a branch optimized for a specific subtype, and then a generic branch. + not guardedByInstanceOf(e.getExpr(), c) and + // Exclude results if "unchecked" warnings are deliberately suppressed. + not e.getEnclosingCallable().suppressesWarningsAbout("unchecked") and + // Report the qualified names if the names are the same. + if coll.getName() = c.getName() + then (abstractName = coll.getQualifiedName() and concreteName = c.getQualifiedName()) + else (abstractName = coll.getName() and concreteName = c.getName()) +select e, "$@ is cast to the concrete type $@, losing abstraction.", + coll.getSourceDeclaration(), abstractName, + c.getSourceDeclaration(), concreteName diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.java b/java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.java new file mode 100644 index 00000000000..c03324fb4ed --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.java @@ -0,0 +1,18 @@ +public class Cart { + private Set items; + // ... + // AVOID: Exposes representation + public Set getItems() { + return items; + } +} +.... +int countItems(Set carts) { + int result = 0; + for (Cart cart : carts) { + Set items = cart.getItems(); + result += items.size(); + items.clear(); // AVOID: Changes internal representation + } + return result; +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.qhelp b/java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.qhelp new file mode 100644 index 00000000000..59934307cb2 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.qhelp @@ -0,0 +1,67 @@ + + + + + +

    A subtle type of defect is caused when an object accidentally exposes its internal +representation to the code outside the object, and the internal representation is then (deliberately or accidentally) +modified in ways that the object is not prepared to handle. Most commonly, this happens +when a getter returns a direct reference to a mutable field within the object, or a setter just assigns +a mutable argument to its field.

    + +
    + +

    There are three ways of addressing this problem:

    + +
      +
    • Using immutable objects : The fields store objects that are immutable, +which means that once constructed their value can never be changed. Examples from the +standard library are String, Integer or +Float. Although such an object may be aliased, or shared between several contexts, +there can be no unexpected changes to the internal state of the object because it cannot be modified.
    • + +
    • Creating a read-only view : The java.util.Collections.unmodifiable* +methods can be used to create a read-only view of a collection without copying it. +This tends to give better performance than creating copies of objects. Note that this +technique is not suitable for every situation, because any changes to the underlying +collection will spread to affect the view. This can lead to unexpected +results, and is a particular danger when writing multi-threaded code.
    • + +
    • Making defensive copies : Each setter (or constructor) makes a copy or clone of the +incoming parameter. In this way, it constructs an instance known only internally, +and no matter what happens with the object that was passed in, the state stays +consistent. Conversely, each getter for a field must also construct a copy of the +field's value to return.
    • +
    + +
    + + +

    In the following example, the private field items is returned +directly by the getter getItems. Thus, a caller obtains a reference to internal object state +and can manipulate the collection of items in the cart. In the example, each of the +carts is emptied when countItems is called.

    + + + +

    The solution is for getItems to return a copy of the +actual field, for example return new HashSet<Item>(items);.

    + +
    + + + +
  • + J. Bloch, Effective Java (second edition), + Items 15 and 39. + Addison-Wesley, 2008. +
  • +
  • + Java 7 API Documentation: Collections. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.ql b/java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.ql new file mode 100644 index 00000000000..f64bcb926b5 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.ql @@ -0,0 +1,118 @@ +/** + * @name Exposing internal representation + * @description An object that accidentally exposes its internal representation may allow the + * object's fields to be modified in ways that the object is not prepared to handle. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/internal-representation-exposure + * @tags reliability + * maintainability + * modularity + * external/cwe/cwe-485 + */ +import java +import semmle.code.java.dataflow.DefUse + +predicate relevantType(RefType t) { + t instanceof Array or + exists(RefType sup | sup = t.getASupertype*().getSourceDeclaration() | + sup.hasQualifiedName("java.util", "Map") or + sup.hasQualifiedName("java.util", "Collection") + ) +} + +predicate modifyMethod(Method m) { + relevantType(m.getDeclaringType()) and ( + m.hasName("add") or m.hasName("addAll") or + m.hasName("put") or m.hasName("putAll") or + m.hasName("push") or m.hasName("pop") or + m.hasName("remove") or m.hasName("removeAll") or + m.hasName("clear") or m.hasName("set") + ) +} + +predicate storesArray(Callable c, int i, Field f) { + f.getDeclaringType() = c.getDeclaringType().getASupertype*().getSourceDeclaration() and + relevantType(f.getType()) and + exists(Parameter p | p = c.getParameter(i) | f.getAnAssignedValue() = p.getAnAccess()) and + not c.isStatic() +} + +predicate returnsArray(Callable c, Field f) { + f.getDeclaringType() = c.getDeclaringType().getASupertype*().getSourceDeclaration() and + relevantType(f.getType()) and + exists(ReturnStmt rs | rs.getEnclosingCallable() = c and rs.getResult() = f.getAnAccess()) and + not c.isStatic() +} + +predicate mayWriteToArray(Expr modified) { + writesToArray(modified) or + + // x = __y__; x[0] = 1; + exists(AssignExpr e, LocalVariableDecl v | e.getDest() = v.getAnAccess() | + modified = e.getSource() and + mayWriteToArray(v.getAnAccess()) + ) or + + // int[] x = __y__; x[0] = 1; + exists(LocalVariableDeclExpr e, Variable v | e.getVariable() = v | + modified = e.getInit() and + mayWriteToArray(v.getAnAccess()) + ) or + + // return __array__; ... method()[1] = 0 + exists(ReturnStmt rs | modified = rs.getResult() and relevantType(modified.getType()) | + exists(Callable enclosing, MethodAccess ma | + enclosing = rs.getEnclosingCallable() and ma.getMethod() = enclosing + | + mayWriteToArray(ma) + ) + ) +} + +predicate writesToArray(Expr array) { + relevantType(array.getType()) and + ( + exists(Assignment a, ArrayAccess access | a.getDest() = access | access.getArray() = array)) or + exists(MethodAccess ma | ma.getQualifier() = array | modifyMethod(ma.getMethod()) + ) +} + +VarAccess modificationAfter(VarAccess v) { + mayWriteToArray(result) and + useUsePair(v, result) +} + +VarAccess varPassedInto(Callable c, int i) { + exists(Call call | call.getCallee() = c | + call.getArgument(i) = result + ) +} + +predicate exposesByReturn(Callable c, Field f, Expr why, string whyText) { + returnsArray(c, f) and + exists(MethodAccess ma | ma.getMethod() = c and ma.getCompilationUnit() != c.getCompilationUnit() | + mayWriteToArray(ma) and + why = ma and + whyText = "after this call to " + c.getName() + ) +} + +predicate exposesByStore(Callable c, Field f, Expr why, string whyText) { + exists(VarAccess v, int i | + storesArray(c, i, f) and + v = varPassedInto(c, i) and + v.getCompilationUnit() != c.getCompilationUnit() and + why = modificationAfter(v) and + whyText = "through the variable " + v.getVariable().getName() + ) +} + +from Callable c, Field f, Expr why, string whyText +where + exposesByReturn(c, f, why, whyText) or + exposesByStore(c, f, why, whyText) +select c, c.getName() + " exposes the internal representation stored in field " + f.getName() + + ". The value may be modified $@.", + why.getLocation(), whyText diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResource.java b/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResource.java new file mode 100644 index 00000000000..e4de1b693a6 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResource.java @@ -0,0 +1,17 @@ +package framework; +class Address { + public URL getPostalCodes() { + // AVOID: The call is made on the run-time type of 'this'. + return this.getClass().getResource("postal-codes.csv"); + } +} + +package client; +class UKAddress extends Address { + public void convert() { + // Looks up "framework/postal-codes.csv" + new Address().getPostalCodes(); + // Looks up "client/postal-codes.csv" + new UKAddress().getPostalCodes(); + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResource.qhelp b/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResource.qhelp new file mode 100644 index 00000000000..fa3d66355bd --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResource.qhelp @@ -0,0 +1,59 @@ + + + + + +

    Using the Class.getResource method is a common way of including +some non-code resources with an application.

    + +

    There are problems when this is called using x.getClass().getResource(), +for some variable x. This is not a safe way to retrieve a resource. The method +getClass returns the run-time class of x (that is, its actual, "most +derived" class, rather than its declared type), which causes two potential problems:

    + +
      +
    • If the run-time type of the receiving object is a subclass of the declared type and is in +a different package, the resource path may be interpreted differently. According to +its contract, Class.getResource qualifies non-absolute paths with the +current package name, thus potentially returning a different resource or failing to find +the requested resource.
    • +
    • Class.getResource delegates finding the resource to the class loader that loaded the class. +At run time, there is no guarantee that all subclasses of +a particular type are loaded by the same class loader, resulting in resource lookup +failures that are difficult to diagnose.
    • +
    + +
    + +

    Rather than using the getClass method, which relies on dynamic dispatch +and run-time types, use class literals instead. For example, instead of calling +getClass().getResource() on an object of type Foo, +call Foo.class.getResource(). Class literals always refer to the +declared type they are used on, removing the dependency on run-time types.

    + +
    + + +

    In the following example, the calls to getPostalCodes return different results, +depending on which class the call is made on: the class Address is in the package +framework and the class UKAddress is in the package client.

    + + + +

    In the following corrected example, the implementation of getPostalCodes is changed +so that it always calls getResource on the same class.

    + + + +
    + + +
  • Java Platform, Standard Edition 7, API Specification: +class.getResource().
  • + + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResource.ql b/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResource.ql new file mode 100644 index 00000000000..979a14a40fe --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResource.ql @@ -0,0 +1,34 @@ +/** + * @name Unsafe use of getResource + * @description Calling 'this.getClass().getResource()' may yield unexpected results if called from a + * subclass in another package. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/unsafe-get-resource + * @tags reliability + * maintainability + */ +import java + +/** Access to a method in `this` object. */ +class MethodAccessInThis extends MethodAccess { + MethodAccessInThis() { + not this.hasQualifier() or + this.getQualifier() instanceof ThisAccess + } +} + +from Class c, MethodAccess getResource, MethodAccessInThis getClass +where + getResource.getNumArgument() = 1 and + ( + getResource.getMethod().hasName("getResource") or + getResource.getMethod().hasName("getResourceAsStream") + ) and + getResource.getQualifier() = getClass and + getClass.getNumArgument() = 0 and + getClass.getMethod().hasName("getClass") and + getResource.getEnclosingCallable().getDeclaringType() = c and + c.isPublic() +select getResource, "The idiom getClass().getResource() is unsafe for classes that may be extended." diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResourceGood.java b/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResourceGood.java new file mode 100644 index 00000000000..2945b3c61bc --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResourceGood.java @@ -0,0 +1,17 @@ +package framework; +class Address { + public URL getPostalCodes() { + // GOOD: The call is always made on an object of the same type. + return Address.class.getResource("postal-codes.csv"); + } +} + +package client; +class UKAddress extends Address { + public void convert() { + // Looks up "framework/postal-codes.csv" + new Address().getPostalCodes(); + // Looks up "framework/postal-codes.csv" + new UKAddress().getPostalCodes(); + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArray.java b/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArray.java new file mode 100644 index 00000000000..c841170728c --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArray.java @@ -0,0 +1,11 @@ +public class Display { + // AVOID: Array constant is vulnerable to mutation. + public static final String[] RGB = { + "FF0000", "00FF00", "0000FF" + }; + + void f() { + // Re-assigning the "constant" is legal. + RGB[0] = "00FFFF"; + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArray.qhelp b/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArray.qhelp new file mode 100644 index 00000000000..089e63c05a0 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArray.qhelp @@ -0,0 +1,62 @@ + + + + + +

    Constant values are typically represented by +public, static, final +fields. When defining several related constants, it is +sometimes tempting to define a public, static, final +field with an array type, and initialize it with a list +of all the different constant values.

    + +

    However, the final keyword applies only +to the field itself (that is, the array reference), +and not to the contents of the array. This means that the field +always refers to the same array instance, but each +element of the array may be modified freely. This possibly +invalidates important assumptions of client code.

    + +
    + + +

    Where possible, avoid declaring array constants. If +there are only a few constant values, consider using +a named constant for each one, or defining them in an enum type.

    + +

    If you genuinely need to +refer to a long list of constants with the same name and +an index, consider replacing the array constant with a +constant of type List to which you assign +an unmodifiable collection. See the example for ways of +achieving this.

    + +
    + + +

    In the following example, public static final applies only to RGB itself, +not the constants that it contains.

    + + + +

    The following example shows examples of ways to declare constants that avoid this problem.

    + + + +
    + + +
  • + J. Bloch, + Effective Java (second edition), p. 70. + Addison-Wesley, 2008. +
  • +
  • Java Language Specification: + 4.12.4 final Variables. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArray.ql b/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArray.ql new file mode 100644 index 00000000000..f5ce6800cae --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArray.ql @@ -0,0 +1,45 @@ +/** + * @name Array constant vulnerable to change + * @description Array constants are mutable and can be changed by malicious code or by accident. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/static-array + * @tags maintainability + * modularity + * external/cwe/cwe-582 + */ +import java + +predicate nonEmptyArrayLiteralOrNull(Expr e) { + exists(ArrayCreationExpr arr | arr = e | + // Array initializer expressions such as `{1, 2, 3}`. + // Array is empty if the initializer expression is empty. + exists(Expr arrayValue | arrayValue = arr.getInit().getAnInit()) + or + // Array creation with dimensions (but without initializers). + // Empty if the first dimension is 0. + exists(Expr dim | dim = arr.getDimension(0) | + not (dim.(CompileTimeConstantExpr).getIntValue() = 0) + ) + ) + or + e instanceof NullLiteral + or + exists(ConditionalExpr cond | cond = e | + nonEmptyArrayLiteralOrNull(cond.getTrueExpr()) and + nonEmptyArrayLiteralOrNull(cond.getFalseExpr()) + ) +} + +from Field f +where + f.isPublic() and + f.isStatic() and + f.isFinal() and + f.getType() instanceof Array and + f.fromSource() and + forall(AssignExpr a | a.getDest() = f.getAnAccess() | + nonEmptyArrayLiteralOrNull(a.getSource()) + ) +select f, "The array constant " + f.getName() + " is vulnerable to mutation." diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArrayGood.java b/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArrayGood.java new file mode 100644 index 00000000000..a48cc7962f5 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArrayGood.java @@ -0,0 +1,42 @@ +// Solution 1: Extract to individual constants +public class Display { + public static final String RED = "FF0000"; + public static final String GREEN = "00FF00"; + public static final String BLUE = "0000FF"; +} + +// Solution 2: Define constants using in an enum type +public enum Display +{ + RED ("FF0000"), GREEN ("00FF00"), BLUE ("0000FF"); + + private String rgb; + private Display(int rgb) { + this.rgb = rgb; + } + public String getRGB(){ + return rgb; + } +} + +// Solution 3: Use an unmodifiable collection +public class Display { + public static final List RGB = + Collections.unmodifiableList( + Arrays.asList("FF0000", + "00FF00", + "0000FF")); +} + +// Solution 4: Use a utility method +public class Utils { + public static List constList(T... values) { + return Collections.unmodifiableList( + Arrays.asList(values)); + } +} + +public class Display { + public static final List RGB = + Utils.constList("FF0000", "00FF00", "0000FF"); +} diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstants.qll b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstants.qll new file mode 100644 index 00000000000..abcc715c11d --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstants.qll @@ -0,0 +1,347 @@ +import java + +/* + * + * Counting nontrivial literal occurrences + * + */ + +private +predicate trivialPositiveIntValue(string s) { + s="0" or s="1" or s="2" or s="3" or s="4" or s="5" or s="6" or s="7" or s="8" or + s="9" or s="10" or s="11" or s="12" or s="13" or s="14" or s="15" or s="16" or s="17" or + s="18" or s="19" or s="20" + + or + + s="16" or s="32" or s="64" or s="128" or s="256" or s="512" or s="1024" or + s="2048" or s="4096" or s="16384" or s="32768" or s="65536" or + s="1048576" or s="2147483648" or s="4294967296" + + or + + s="15" or s="31" or s="63" or s="127" or s="255" or s="511" or s="1023" or + s="2047" or s="4095" or s="16383" or s="32767" or s="65535" or + s="1048577" or s="2147483647" or s="4294967295" + + or + + s = "0x00000001" or s = "0x00000002" or s = "0x00000004" or s = "0x00000008" or + s = "0x00000010" or s = "0x00000020" or s = "0x00000040" or s = "0x00000080" or + s = "0x00000100" or s = "0x00000200" or s = "0x00000400" or s = "0x00000800" or + s = "0x00001000" or s = "0x00002000" or s = "0x00004000" or s = "0x00008000" or + s = "0x00010000" or s = "0x00020000" or s = "0x00040000" or s = "0x00080000" or + s = "0x00100000" or s = "0x00200000" or s = "0x00400000" or s = "0x00800000" or + s = "0x01000000" or s = "0x02000000" or s = "0x04000000" or s = "0x08000000" or + s = "0x10000000" or s = "0x20000000" or s = "0x40000000" or s = "0x80000000" or + s = "0x00000001" or s = "0x00000003" or s = "0x00000007" or s = "0x0000000f" or + s = "0x0000001f" or s = "0x0000003f" or s = "0x0000007f" or s = "0x000000ff" or + s = "0x000001ff" or s = "0x000003ff" or s = "0x000007ff" or s = "0x00000fff" or + s = "0x00001fff" or s = "0x00003fff" or s = "0x00007fff" or s = "0x0000ffff" or + s = "0x0001ffff" or s = "0x0003ffff" or s = "0x0007ffff" or s = "0x000fffff" or + s = "0x001fffff" or s = "0x003fffff" or s = "0x007fffff" or s = "0x00ffffff" or + s = "0x01ffffff" or s = "0x03ffffff" or s = "0x07ffffff" or s = "0x0fffffff" or + s = "0x1fffffff" or s = "0x3fffffff" or s = "0x7fffffff" or s = "0xffffffff" or + s = "0x0001" or s = "0x0002" or s = "0x0004" or s = "0x0008" or + s = "0x0010" or s = "0x0020" or s = "0x0040" or s = "0x0080" or + s = "0x0100" or s = "0x0200" or s = "0x0400" or s = "0x0800" or + s = "0x1000" or s = "0x2000" or s = "0x4000" or s = "0x8000" or + s = "0x0001" or s = "0x0003" or s = "0x0007" or s = "0x000f" or + s = "0x001f" or s = "0x003f" or s = "0x007f" or s = "0x00ff" or + s = "0x01ff" or s = "0x03ff" or s = "0x07ff" or s = "0x0fff" or + s = "0x1fff" or s = "0x3fff" or s = "0x7fff" or s = "0xffff" or + s = "0x01" or s = "0x02" or s = "0x04" or s = "0x08" or + s = "0x10" or s = "0x20" or s = "0x40" or s = "0x80" or + s = "0x01" or s = "0x03" or s = "0x07" or s = "0x0f" or + s = "0x1f" or s = "0x3f" or s = "0x7f" or s = "0xff" or + s = "0x00" + + or + + s = "10" or s = "100" or s = "1000" or + s = "10000" or s = "100000" or s = "1000000" or + s = "10000000" or s = "100000000" or s = "1000000000" +} + +private +predicate trivialIntValue(string s) { + trivialPositiveIntValue(s) or + exists(string pos | trivialPositiveIntValue(pos) and s = "-" + pos) +} + +private +predicate intTrivial(Literal lit) { + exists(string v | trivialIntValue(v) and v = lit.getLiteral()) +} + +private +predicate longTrivial(Literal lit) { + exists(string v | trivialIntValue(v) and v + "L" = lit.getLiteral()) +} + +private +predicate powerOfTen(float f) { + f = 10 or f = 100 or f = 1000 or + f = 10000 or f = 100000 or f = 1000000 or + f = 10000000 or f = 100000000 or f = 1000000000 +} + +private +predicate floatTrivial(Literal lit) { + (lit instanceof FloatingPointLiteral or lit instanceof DoubleLiteral) and + exists(float f | + f = lit.getValue().toFloat() and + (f.abs() <= 20.0 or powerOfTen(f)) + ) +} + +private +predicate stringTrivial(StringLiteral lit) { + lit.getLiteral().length() < 8 +} + +private +predicate small(Literal lit) { + lit.getLiteral().length() <= 1 +} + +private +predicate trivial(Literal lit) { + lit instanceof CharacterLiteral or + lit instanceof BooleanLiteral or + lit instanceof NullLiteral or + intTrivial(lit) or + floatTrivial(lit) or + stringTrivial(lit) or + longTrivial(lit) or + small(lit) or + excludedLiteral(lit) +} + +private +predicate literalIsConstantInitializer(Literal literal, Field f) { + exists(AssignExpr e, VarAccess access | + access = e.getAChildExpr() and + f = access.getVariable() and + access.getIndex() = 0 and + f.isFinal() and + f.isStatic() and + literal = e.getAChildExpr() and + literal.getIndex() = 1) and + not trivial(literal) +} + +private +predicate nonTrivialValue(string value, Literal literal, string context) { + value = literal.getLiteral() and + not trivial(literal) and + not literalIsConstantInitializer(literal, _) and + not literal.getParent*() instanceof ArrayInit and + not literal.getParent+() instanceof Annotation and + exists(MethodAccess ma | literal = ma.getAnArgument() and ma.getMethod().getName() = context) +} + + +private +predicate valueOccurrenceCount(string value, int n, string context) { + n = strictcount(Literal lit | nonTrivialValue(value, lit, context)) and + n > 20 +} + +private +predicate occurenceCount(Literal lit, string value, int n, string context) { + valueOccurrenceCount(value, n, context) and + value = lit.getLiteral() and + nonTrivialValue(_, lit, context) +} + + +/* + * + * Literals repeated frequently + * + */ + +private +predicate check(Literal lit, string value, int n, string context, CompilationUnit f) { + // Check that the literal is nontrivial + not trivial(lit) and + // Check that it is repeated a number of times + occurenceCount(lit, value, n, context) and n > 20 and + f = lit.getCompilationUnit() +} + +private +predicate checkWithFileCount(string value, int overallCount, int fileCount, string context, CompilationUnit f) { + fileCount = strictcount(Literal lit | check(lit, value, overallCount, context, f)) +} + +private +predicate firstOccurrence(Literal lit, string value, string context, int n) { + exists(CompilationUnit f, int fileCount | + checkWithFileCount(value, n, fileCount, context, f) and + fileCount < 100 and + check(lit, value, n, context, f) and + not exists(Literal lit2, int start1, int start2 | + check(lit2, value, n, context, f) and + lit.getLocation().getStartLine() = start1 and + lit2.getLocation().getStartLine() = start2 and + start2 < start1) + ) +} + +predicate isNumber(Literal lit) { + lit.getType().getName() = "char" or + lit.getType().getName() = "short" or + lit.getType().getName() = "int" or + lit.getType().getName() = "long" or + lit.getType().getName() = "float" or + lit.getType().getName() = "double" +} + +predicate magicConstant(Literal e, string msg) { + exists(string value, int n, string context | + firstOccurrence(e, value, context, n) and + msg = "Magic constant: literal '" + value + "' is used " + n.toString() + " times in calls to " + context + ) +} + +/* + * + * Literals where there is a defined constant with the same value + * + */ + +private +predicate relevantField(Field f, string value) { + exists(Literal lit | not trivial(lit) and value = lit.getLiteral() and literalIsConstantInitializer(lit, f)) +} + +private +predicate relevantType(RefType t, string value, Package p) { + exists(Literal lit | nonTrivialValue(value, lit, _) | + lit.getEnclosingCallable().getDeclaringType() = t and p = t.getPackage() + ) +} + +private +predicate fieldUsedInContext(Field constField, string context) { + literalIsConstantInitializer(_, constField) and + exists(MethodAccess ma | + constField.getAnAccess() = ma.getAnArgument() and + ma.getMethod().getName() = context) +} + +private +predicate candidateConstantForLiteral(Field constField, RefType literalType, Literal magicLiteral, string context) { + exists(Literal initLiteral | + literalIsConstantInitializer(initLiteral, constField) and + exists(string value | + value = initLiteral.getLiteral() and + nonTrivialValue(value, magicLiteral, context) and + fieldUsedInContext(constField, context) + ) and + literalType = magicLiteral.getEnclosingCallable().getDeclaringType() + ) +} + +private +RefType inheritsProtected(Field f) { + (f.isProtected() and result.getASupertype() = f.getDeclaringType()) or + exists(RefType mid | mid = inheritsProtected(f) and result.getASupertype() = mid) +} + +private +predicate constantForLiteral(Field field, string value, RefType fromType, Literal magicLiteral, string context) { + //public fields in public classes + ( + candidateConstantForLiteral(field, fromType, magicLiteral, context) and + relevantField(field, value) and + field.getDeclaringType().isPublic() and + field.isPublic() and + relevantType(fromType, value, _) + ) + or + //in same class + ( + candidateConstantForLiteral(field, fromType, magicLiteral, context) and + relevantField(field, value) and + fromType = field.getDeclaringType() and + relevantType(fromType, value, _) + ) + or + //in subclass and not private + ( + candidateConstantForLiteral(field, fromType, magicLiteral, context) and + relevantField(field, value) and + field.isProtected() and + fromType = inheritsProtected(field) and + relevantType(fromType, value, _) + ) + or + //not private and in same package + ( + candidateConstantForLiteral(field, fromType, magicLiteral, context) and + relevantField(field, value) and + field.isPackageProtected() and + exists(Package p | + exists(CompilationUnit cu | cu = field.getCompilationUnit() and cu.getPackage() = p) and + relevantType(fromType, value, p) + ) + ) +} + +private +predicate canUseFieldInsteadOfLiteral(Field constField, Literal magicLiteral, string context) { + constantForLiteral(constField, _, _, magicLiteral, context) +} + +predicate literalInsteadOfConstant(Literal magicLiteral, string message, Field constField, string linkText) { + exists(string context | + canUseFieldInsteadOfLiteral(constField, magicLiteral, context) and + message = + "Literal value '" + magicLiteral.getLiteral() + "' used " + + " in a call to " + context + + "; consider using the defined constant $@." and linkText = constField.getName() + and + ( + constField.getCompilationUnit() = magicLiteral.getCompilationUnit() or + not almostPrivate(constField) + ) + and + ( + constField.getCompilationUnit().getPackage() = magicLiteral.getCompilationUnit().getPackage() or + not almostPackageProtected(constField) + ) + ) +} + +private predicate almostPrivate(Field f) { + not exists(VarAccess va | va.getVariable() = f and va.getCompilationUnit() != f.getCompilationUnit()) + or + exists(Interface i | i = f.getDeclaringType() | + forall(VarAccess va | va.getVariable() = f | + va.getEnclosingCallable().getDeclaringType().getASupertype*() = i + ) + ) +} + +private predicate almostPackageProtected(Field f) { + not exists(VarAccess va | va.getVariable() = f | + va.getCompilationUnit().getPackage() != f.getCompilationUnit().getPackage() + ) +} + +/* + * + * Removing literals from uninteresting contexts + * + */ + +private +predicate excludedLiteral(Literal lit) { + // Remove test cases + lit.getEnclosingCallable().getDeclaringType() instanceof TestClass + or + exists(MethodAccess ma | lit = ma.getAnArgument() | ma.getMethod() instanceof TestMethod) +} diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsNumbers.java b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsNumbers.java new file mode 100644 index 00000000000..12891b56e17 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsNumbers.java @@ -0,0 +1,36 @@ +// Problem version +public class MagicConstants +{ + final static public String IP = "127.0.0.1"; + final static public int PORT = 8080; + final static public String USERNAME = "test"; + + public void serve(String ip, int port, String user, int timeout) { + // ... + } + + public static void main(String[] args) { + int timeout = 60000; // AVOID: Magic number + + new MagicConstants().serve(IP, PORT, USERNAME, timeout); + } +} + + +// Fixed version +public class MagicConstants +{ + final static public String IP = "127.0.0.1"; + final static public int PORT = 8080; + final static public String USERNAME = "test"; + final static public int TIMEOUT = 60000; // Magic number is replaced by named constant + + public void serve(String ip, int port, String user, int timeout) { + // ... + } + + public static void main(String[] args) { + + new MagicConstants().serve(IP, PORT, USERNAME, TIMEOUT); // Use 'TIMEOUT' constant + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsNumbers.qhelp b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsNumbers.qhelp new file mode 100644 index 00000000000..1c675e2b717 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsNumbers.qhelp @@ -0,0 +1,53 @@ + + + + + +

    A magic number is a numeric literal (for example, 8080, +2048) that is used in the middle of a block of code without +explanation. It is considered bad practice to use magic numbers because:

    + +
      +
    • A number in isolation can be difficult for other programmers to understand. +
    • +
    • It can be difficult to update the code if the requirements change. For example, if the magic +number represents the number of guests allowed, adding one more guest means that you must change +every occurrence of the magic number. +
    • +
    + +
    + + +

    Assign the magic number to a new named constant, and use this instead. This overcomes the two problems +with magic numbers:

    + +
      +
    • A named constant (such as MAX_GUESTS) is more easily understood by other programmers. +
    • +
    • Using the same named constant in many places makes the code much easier to +update if the requirements change, because you have to update the number in only one place. +
    • +
    + +
    + + +

    The following example shows a magic number timeout. This should be replaced by a new +named constant, as shown in the fixed version.

    + + + +
    + + + +
  • +R. C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship, §17.G25. Prentice Hall, 2008. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsNumbers.ql b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsNumbers.ql new file mode 100644 index 00000000000..b60fd486f3c --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsNumbers.ql @@ -0,0 +1,20 @@ +/** + * @name Magic numbers + * @description A magic number makes code less readable and maintainable. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/magic-number + * @tags maintainability + * readability + * statistical + * non-attributable + */ +import java +import MagicConstants + +from Literal e, string msg +where + magicConstant(e, msg) and + isNumber(e) +select e, msg diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsString.java b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsString.java new file mode 100644 index 00000000000..e3cdaa5cb80 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsString.java @@ -0,0 +1,36 @@ +// Problem version +public class MagicConstants +{ + final static public String IP = "127.0.0.1"; + final static public int PORT = 8080; + final static public int TIMEOUT = 60000; + + public void serve(String ip, int port, String user, int timeout) { + // ... + } + + public static void main(String[] args) { + String username = "test"; // AVOID: Magic string + + new MagicConstants().serve(IP, PORT, username, TIMEOUT); + } +} + + +// Fixed version +public class MagicConstants +{ + final static public String IP = "127.0.0.1"; + final static public int PORT = 8080; + final static public int USERNAME = "test"; // Magic string is replaced by named constant + final static public int TIMEOUT = 60000; + + public void serve(String ip, int port, String user, int timeout) { + // ... + } + + public static void main(String[] args) { + + new MagicConstants().serve(IP, PORT, USERNAME, TIMEOUT); // Use 'USERNAME' constant + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsString.qhelp b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsString.qhelp new file mode 100644 index 00000000000..8d0a7913f5f --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsString.qhelp @@ -0,0 +1,53 @@ + + + + + +

    A magic string is a string literal (for example, "SELECT", +"127.0.0.1") that is used in the middle of a block of code without +explanation. It is considered bad practice to use magic strings because:

    + +
      +
    • A string in isolation can be difficult for other programmers to understand. +
    • +
    • It can be difficult to update the code if the requirements change. For example, if the magic +string represents a protocol, changing the protocol means that you must change +every occurrence of the protocol. +
    • +
    + +
    + + +

    Assign the magic string to a new named constant, and use this instead. This overcomes the two problems +with magic strings:

    + +
      +
    • A named constant (such as SMTP_HELO) is more easily understood by other programmers. +
    • +
    • Using the same named constant in many places makes the code much easier to +update if the requirements change, because you have to update the string in only one place. +
    • +
    + +
    + + +

    The following example shows a magic string username. This should be replaced by a new +named constant, as shown in the fixed version.

    + + + +
    + + + +
  • +R. C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship, §17.G25. Prentice Hall, 2008. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsString.ql b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsString.ql new file mode 100644 index 00000000000..3e69696daa4 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsString.ql @@ -0,0 +1,78 @@ +/** + * @name Magic strings + * @description A magic string makes code less readable and maintainable. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/magic-string + * @tags maintainability + * readability + * statistical + * non-attributable + */ +import java +import MagicConstants + +/** + * Holds if the string is a standard system property as defined in: + * + * http://docs.oracle.com/javase/7/docs/api/java/lang/System.html#getProperties() + */ +predicate isSystemProperty(string e) { + e = "java.version" or + e = "java.vendor" or + e = "java.vendor.url" or + e = "java.home" or + e = "java.vm.specification.version" or + e = "java.vm.specification.vendor" or + e = "java.vm.specification.name" or + e = "java.vm.version" or + e = "java.vm.vendor" or + e = "java.vm.name" or + e = "java.specification.version" or + e = "java.specification.vendor" or + e = "java.specification.name" or + e = "java.class.version" or + e = "java.class.path" or + e = "java.library.path" or + e = "java.io.tmpdir" or + e = "java.compiler" or + e = "java.ext.dirs" or + e = "os.name" or + e = "os.arch" or + e = "os.version" or + e = "file.separator" or + e = "path.separator" or + e = "line.separator" or + e = "user.name" or + e = "user.home" or + e = "user.dir" +} + +predicate trivialContext(Literal e) { + // String concatenation. + e.getParent() instanceof AddExpr or + e.getParent() instanceof AssignAddExpr or + exists(MethodAccess ma | + ma.getMethod().getName() = "append" and + (e = ma.getAnArgument() or e = ma.getQualifier()) + ) or + // Standard property in a call to `System.getProperty()`. + exists(MethodAccess ma | + ma.getMethod().getName() = "getProperty" and + e = ma.getAnArgument() and + ma.getMethod().getDeclaringType() instanceof TypeSystem and + isSystemProperty(e.getValue()) + ) or + // Message in an exception. + exists(ClassInstanceExpr constr | + constr.getType().(RefType).getASupertype+().hasName("Exception") and + e = constr.getArgument(0) + ) +} + +from StringLiteral e, string msg +where + magicConstant(e, msg) and + not trivialContext(e) +select e, msg diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConstant.java b/java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConstant.java new file mode 100644 index 00000000000..beb2916613b --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConstant.java @@ -0,0 +1,37 @@ +// Problem version +public class MagicConstants +{ + final static public String IP = "127.0.0.1"; + final static public int PORT = 8080; + final static public String USERNAME = "test"; + final static public int TIMEOUT = 60000; + + public void serve(String ip, int port, String user, int timeout) { + // ... + } + + public static void main(String[] args) { + int internal_port = 8080; // AVOID: Magic number + + new MagicConstants().serve(IP, internal_port, USERNAME, TIMEOUT); + } +} + + +// Fixed version +public class MagicConstants +{ + final static public String IP = "127.0.0.1"; + final static public int PORT = 8080; + final static public String USERNAME = "test"; + final static public int TIMEOUT = 60000; + + public void serve(String ip, int port, String user, int timeout) { + // ... + } + + public static void main(String[] args) { + + new MagicConstants().serve(IP, PORT, USERNAME, TIMEOUT); // Use 'PORT' constant + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConstant.qhelp b/java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConstant.qhelp new file mode 100644 index 00000000000..0dc6679535e --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConstant.qhelp @@ -0,0 +1,53 @@ + + + + + +

    A magic number is a numeric literal (for example, 8080, +2048) that is used in the middle of a block of code without +explanation. It is considered bad practice to use magic numbers because:

    + +
      +
    • A number in isolation can be difficult for other programmers to understand. +
    • +
    • It can be difficult to update the code if the requirements change. For example, if the magic +number represents the number of guests allowed, adding one more guest means that you must change +every occurrence of the magic number. +
    • +
    + +
    + + +

    Replace the magic number with the existing named constant. This overcomes the two problems +with magic numbers:

    + +
      +
    • A named constant (such as MAX_GUESTS) is more easily understood by other programmers. +
    • +
    • Using the same named constant in many places makes the code much easier to +update if the requirements change, because you have to update the number in only one place. +
    • +
    + +
    + + +

    The following example shows a magic number internal_port. This should be replaced by +the existing named constant, as shown in the fixed version.

    + + + +
    + + + +
  • +R. C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship, §17.G25. Prentice Hall, 2008. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConstant.ql b/java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConstant.ql new file mode 100644 index 00000000000..5f030dd7da8 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConstant.ql @@ -0,0 +1,19 @@ +/** + * @name Magic numbers: use defined constant + * @description A magic number, which is used instead of an existing named constant, makes code less + * readable and maintainable. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/use-number-constant + * @tags maintainability + * readability + */ +import java +import MagicConstants + +from Literal magicLiteral, string message, Field field, string linkText +where + isNumber(magicLiteral) and + literalInsteadOfConstant(magicLiteral, message, field, linkText) +select magicLiteral, message, field, linkText diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.java b/java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.java new file mode 100644 index 00000000000..b4b7c116e73 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.java @@ -0,0 +1,37 @@ +// Problem version +public class MagicConstants +{ + final static public String IP = "127.0.0.1"; + final static public int PORT = 8080; + final static public String USERNAME = "test"; + final static public int TIMEOUT = 60000; + + public void serve(String ip, int port, String user, int timeout) { + // ... + } + + public static void main(String[] args) { + String internal_ip = "127.0.0.1"; // AVOID: Magic string + + new MagicConstants().serve(internal_ip, PORT, USERNAME, TIMEOUT); + } +} + + +// Fixed version +public class MagicConstants +{ + final static public String IP = "127.0.0.1"; + final static public int PORT = 8080; + final static public String USERNAME = "test"; + final static public int TIMEOUT = 60000; + + public void serve(String ip, int port, String user, int timeout) { + // ... + } + + public static void main(String[] args) { + + new MagicConstants().serve(IP, PORT, USERNAME, TIMEOUT); //Use 'IP' constant + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.qhelp b/java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.qhelp new file mode 100644 index 00000000000..1a6aa84b134 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.qhelp @@ -0,0 +1,53 @@ + + + + + +

    A magic string is a string literal (for example, "SELECT", +"127.0.0.1") that is used in the middle of a block of code without +explanation. It is considered bad practice to use magic strings because:

    + +
      +
    • A string in isolation can be difficult for other programmers to understand. +
    • +
    • It can be difficult to update the code if the requirements change. For example, if the magic +string represents a protocol, changing the protocol means that you must change +every occurrence of the protocol. +
    • +
    + +
    + + +

    Replace the magic string with the existing named constant. This overcomes the two problems +with magic strings:

    + +
      +
    • A named constant (such as SMTP_HELO) is more easily understood by other programmers. +
    • +
    • Using the same named constant in many places makes the code much easier to +update if the requirements change, because you have to update the string in only one place. +
    • +
    + +
    + + +

    The following example shows a magic string internal_ip. This should be replaced by +the existing named constant, as shown in the fixed version.

    + + + +
    + + + +
  • +R. C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship, §17.G25. Prentice Hall, 2008. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.ql b/java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.ql new file mode 100644 index 00000000000..e255165848f --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.ql @@ -0,0 +1,17 @@ +/** + * @name Magic strings: use defined constant + * @description A magic string, which is used instead of an existing named constant, makes code less + * readable and maintainable. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/use-string-constant + * @tags maintainability + * readability + */ +import java +import MagicConstants + +from StringLiteral magicLiteral, string message, Field field, string linkText +where literalInsteadOfConstant(magicLiteral, message, field, linkText) +select magicLiteral, message, field, linkText diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.java b/java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.java new file mode 100644 index 00000000000..7a1d1cc86e1 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.java @@ -0,0 +1,24 @@ +public class Outer +{ + void printMessage() { + System.out.println("Outer"); + } + + class Inner extends Super + { + void ambiguous() { + printMessage(); // Ambiguous call + } + } + + public static void main(String[] args) { + new Outer().new Inner().ambiguous(); + } +} + +class Super +{ + void printMessage() { + System.out.println("Super"); + } +} diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.qhelp b/java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.qhelp new file mode 100644 index 00000000000..ad4515d7021 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.qhelp @@ -0,0 +1,48 @@ + + + + + +

    If a call is made to a method from an inner class A, and a method of that name is +defined in both a superclass of A and an outer class of A, it is not clear to a programmer +which method is intended to be called.

    + +
    + + +

    In the following example, it is not clear whether the call to printMessage calls the +method that is defined in Outer or Super.

    + + + +

    Inherited methods take precedence over methods in outer classes, so the method in the superclass +is called. However, such situations are a potential cause of confusion and defects.

    + +
    + + +

    Resolve the ambiguity by explicitly qualifying the method call:

    + +
      +
    • To specify the outer class, prefix the method with Outer.this..
    • +
    • To specify the superclass, prefix the method with super..
    • +
    + +

    In the above example, the call to printMessage could be replaced by +either Outer.this.printMessage or super.printMessage, depending on which +method you intend to call. To preserve the behavior in the example, use super.printMessage.

    + +
    + + + +
  • +Inner Classes Specification: +What are top-level classes and inner classes?. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.ql b/java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.ql new file mode 100644 index 00000000000..cb9791add95 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.ql @@ -0,0 +1,59 @@ +/** + * @name Subtle call to inherited method + * @description An unqualified call to a method that exists with the same signature in both a + * superclass and an outer class is ambiguous. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id java/subtle-inherited-call + * @tags reliability + * readability + */ +import java + +RefType nestedSupertypePlus(RefType t) { + t.getASourceSupertype() = result and + t instanceof NestedType or + exists(RefType mid | mid = nestedSupertypePlus(t) | + mid.getASourceSupertype() = result + ) +} + +/** + * A call (without a qualifier) in a nested type + * to an inherited method with the specified `signature`. + */ +predicate callToInheritedMethod(RefType lexicalScope, MethodAccess ma, string signature) { + not ma.getMethod().isStatic() and + not ma.hasQualifier() and + ma.getEnclosingCallable().getDeclaringType() = lexicalScope and + nestedSupertypePlus(lexicalScope).getAMethod() = ma.getMethod() and + signature = ma.getMethod().getSignature() +} + +/** + * Return accessible methods in an outer class of `nested`. + * + * Accessible means that if a method is virtual then none of the nested + * classes "on-route" can be static. + */ +Method methodInEnclosingType(NestedType nested, string signature) { + (result.isStatic() or not nested.isStatic()) + and + result.getSignature() = signature + and + exists(RefType outer | outer = nested.getEnclosingType() | + result = outer.getAMethod() or + result = methodInEnclosingType(nested, signature) + ) +} + +from MethodAccess ma, Method m, NestedType nt, string signature +where + callToInheritedMethod(nt, ma, signature) and + m = methodInEnclosingType(nt, signature) and + // There is actually scope for confusion. + not nt.getASourceSupertype+() = m.getDeclaringType() +select ma, "A $@ is called instead of a $@.", + ma.getMethod(), "method declared in a superclass", + m, "method with the same signature in an enclosing class" diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.java b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.java new file mode 100644 index 00000000000..24bc7bdb7b5 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.java @@ -0,0 +1,18 @@ +public class InternetResource +{ + private String protocol; + private String host; + private String path; + + // ... + + public String toUri() { + return protocol + "://" + host + "/" + path; + } + + // ... + + public String toURI() { + return toUri(); + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.qhelp b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.qhelp new file mode 100644 index 00000000000..6479ef09840 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.qhelp @@ -0,0 +1,35 @@ + + + + + +

    It is bad practice to have methods in a class with names that differ +only in their capitalization. This can be confusing and lead to mistakes.

    + +
    + + +

    Name the methods to make the distinction between them +clear.

    + +
    + + +

    The following example shows a class that contains two methods: toUri and +toURI. One or both of them should be renamed.

    + + + +
    + + + +
  • +R. C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship, 17.N4. Prentice Hall, 2008. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.ql b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.ql new file mode 100644 index 00000000000..27f5561be57 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.ql @@ -0,0 +1,29 @@ +/** + * @name Confusing method names because of capitalization + * @description Methods in the same class whose names differ only in capitalization are + * confusing. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/confusing-method-name + * @tags maintainability + * readability + * naming + */ +import java + +predicate methodTypeAndLowerCaseName(Method m, RefType t, string name) { + t = m.getDeclaringType() and + name = m.getName().toLowerCase() +} + +from Method m, Method n +where + exists(RefType t, string name | + methodTypeAndLowerCaseName(m, t, name) and + methodTypeAndLowerCaseName(n, t, name) + ) and + not m.getAnAnnotation() instanceof DeprecatedAnnotation and + not n.getAnAnnotation() instanceof DeprecatedAnnotation and + m.getName() < n.getName() +select m, "The method '" + m.getName() + "' may be confused with $@.", n, n.getName() diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.qhelp b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.qhelp new file mode 100644 index 00000000000..39a9b635744 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.qhelp @@ -0,0 +1,51 @@ + + + + + +

    Overloaded method declarations that have the same number of parameters may be confusing +if none of the corresponding pairs of parameter types is substantially different. A pair of +parameter types A and B is substantially different if A cannot be cast to B and B cannot be cast +to A. If the parameter types are not substantially different then the programmer may assume that the method with parameter type A is called when in fact +the method with parameter type B is called.

    + +
    + + +

    It is generally best to avoid +declaring overloaded methods with the same number of parameters, unless at least one of +the corresponding parameter pairs is substantially different. +

    + +
    + + +

    +Declaring overloaded methods process(Object obj) and process(String s) +is confusing because the parameter types are not substantially different. It is clearer to declare +methods with different names: processObject(Object obj) and +processString(String s).

    + +

    In contrast, declaring overloaded methods process(Object obj, String s) and +process(String s, int i) is not as confusing because the second parameters of each +method are substantially different.

    + +
    + + + +
  • + J. Bloch, Effective Java (second edition), + Item 41. + Addison-Wesley, 2008. +
  • +
  • + Java Language Specification: + 15.12 Method Invocation Expressions. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql new file mode 100644 index 00000000000..cd754096245 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql @@ -0,0 +1,123 @@ +/** + * @name Confusing overloading of methods + * @description Overloaded methods that have the same number of parameters, where each pair of + * corresponding parameter types is convertible by casting or autoboxing, may be + * confusing. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/confusing-method-signature + * @tags maintainability + * readability + * naming + */ +import java + +private pragma[nomagic] +predicate confusingPrimitiveBoxedTypes(Type t, Type u) { + t.(PrimitiveType).getBoxedType() = u or + u.(PrimitiveType).getBoxedType() = t +} + +private +predicate overloadedMethods(Method n, Method m) { + n.fromSource() and + exists(RefType rt, string name, int numParams | + candidateMethod(rt, m, name, numParams) and + candidateMethod(rt, n, name, numParams) + ) and + n != m and + n.getSourceDeclaration().getSignature() < m.getSourceDeclaration().getSignature() +} + +private +predicate overloadedMethodsMostSpecific(Method n, Method m) { + overloadedMethods(n, m) and + not exists(Method nSup, Method mSup | + n.overridesOrInstantiates*(nSup) and m.overridesOrInstantiates*(mSup) + | + overloadedMethods(nSup, mSup) and + (n != nSup or m != mSup) + ) +} + +/** + * A whitelist of names that are commonly overloaded in odd ways and should + * not be reported by this query. + */ +private predicate whitelist(string name) { + name = "visit" +} + +/** + * Method `m` has name `name`, number of parameters `numParams` + * and is declared in `t` or inherited from a supertype of `t`. + */ +private +predicate candidateMethod(RefType t, Method m, string name, int numParam) { + exists(Method n | n.getSourceDeclaration() = m | t.inherits(n)) and + m.getName() = name and + m.getNumberOfParameters() = numParam and + m = m.getSourceDeclaration() and + not m.getAnAnnotation() instanceof DeprecatedAnnotation and + not whitelist(name) +} + +private pragma[inline] +predicate potentiallyConfusingTypes(Type a, Type b) { + exists(RefType commonSubtype | hasSubtypeOrInstantiation*(a, commonSubtype) | + hasSubtypeOrInstantiation*(b, commonSubtype) + ) or + confusingPrimitiveBoxedTypes(a, b) +} + +private +predicate hasSubtypeOrInstantiation(RefType t, RefType sub) { + hasSubtype(t, sub) or + sub.getSourceDeclaration() = t +} + +private +predicate confusinglyOverloaded(Method m, Method n) { + overloadedMethodsMostSpecific(n, m) and + forall(int i, Parameter p, Parameter q | p = n.getParameter(i) and q = m.getParameter(i) | + potentiallyConfusingTypes(p.getType(), q.getType()) + ) and + // There is no possibility for confusion between two methods with identical behavior. + not exists(Method target | delegate*(m, target) and delegate*(n, target)) +} + +private +predicate wrappedAccess(Expr e, MethodAccess ma) { + e = ma or + wrappedAccess(e.(ParExpr).getExpr(), ma) or + wrappedAccess(e.(CastExpr).getExpr(), ma) +} + +private +predicate delegate(Method caller, Method callee) { + exists(MethodAccess ma | ma.getMethod() = callee | + exists(Stmt stmt | stmt = caller.getBody().(SingletonBlock).getStmt() | + wrappedAccess(stmt.(ExprStmt).getExpr(), ma) or + wrappedAccess(stmt.(ReturnStmt).getResult(), ma) + ) and + forex(Parameter p, int i, Expr arg | p = caller.getParameter(i) and ma.getArgument(i) = arg | + // The parameter is propagated without modification. + arg = p.getAnAccess() or + // The parameter is cast to a supertype. + arg.(CastExpr).getExpr() = p.getAnAccess() and + arg.getType().(RefType).getASubtype() = p.getType() + ) + ) +} + +from Method m, Method n, string messageQualifier +where + confusinglyOverloaded(m, n) and + (if m.getDeclaringType() = n.getDeclaringType() + then messageQualifier = "" + else messageQualifier = m.getDeclaringType().getName() + ".") +select n, "Method " + n.getDeclaringType() + "." + n + + "(..) could be confused with overloaded method $@, since dispatch depends on static types.", + m.getSourceDeclaration(), messageQualifier + m.getName() + diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.java b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.java new file mode 100644 index 00000000000..618bb53090b --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.java @@ -0,0 +1,12 @@ +public class Customer +{ + private String title; + private String forename; + private String surname; + + // ... + + public String tostring() { // Incorrect capitalization of 'toString' + return title + " " + forename + " " + surname; + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.qhelp b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.qhelp new file mode 100644 index 00000000000..248b3d88b89 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.qhelp @@ -0,0 +1,45 @@ + + + + + +

    If a method that would override another method but does not +because the name is capitalized differently, there are two possibilities:

    + +
      +
    • The programmer intends the method to override the other method, and the + difference in capitalization is a typographical error.
    • +
    • The programmer does not intend the method to override the other method, + in which case the similarity of the names is very confusing.
    • +
    + +
    + + +

    If overriding is intended, make the capitalization of the +two methods the same.

    + +

    If overriding is not intended, consider naming the methods to make +the distinction between them clear.

    + +
    + + +

    In the following example, toString has been +wrongly capitalized as tostring. This means that objects of type +Customer do not print correctly.

    + + + +
    + + + +
  • +R. C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship, §17.N4. Prentice Hall, 2008. +
  • + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.ql b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.ql new file mode 100644 index 00000000000..caeca946f07 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.ql @@ -0,0 +1,75 @@ +/** + * @name Confusing method names because of overriding + * @description A method that would override another method but does not, because the name is + * capitalized differently, is confusing and may be a mistake. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/confusing-override-name + * @tags maintainability + * readability + * naming + */ +import java + +/** + * For each class, get all methods from this class or its + * superclasses, with their names in lowercase. + */ +predicate methodNames(RefType t, Method m, string lowercase, string name) { + exists(RefType t2 | + m.getDeclaringType() = t2 and + hasSubtype*(t2, t) + ) and + name = m.getName() and + lowercase = name.toLowerCase() and + lowercase.length() > 1 +} + +/** + * For each class, find the pairs of methods that + * are candidates for being confusing in this class. + */ +predicate confusing(Method m1, Method m2) { + exists(RefType t, string lower, string name1, string name2 | + methodNames(t, m1, lower, name1) and + methodNames(t, m2, lower, name2) and + name1 != name2 + ) +} + +/* + * Two methods are considered confusing if all of the following conditions hold: + * + * - They are both static methods or both instance methods. + * - They are not declared in the same class, and the superclass method is + * not overridden in an intermediate class. + * - They have different names. + * - They have the same names if case is ignored. + * - There is no method in the subclass that has the same name as + * the superclass method. + * + * There is an additional check that only methods with names longer than one character + * can be considered confusing. + */ + +from Method m1, Method m2 +where + confusing(m1, m2) and + m1.getDeclaringType() != m2.getDeclaringType() and + ( + m1.isStatic() and m2.isStatic() + or + not m1.isStatic() and not m2.isStatic() + ) and + not exists(Method mid | + confusing(m1, mid) and + mid.getDeclaringType().getASupertype+() = m2.getDeclaringType() + ) and + not exists(Method notConfusing | + notConfusing.getDeclaringType() = m1.getDeclaringType() and + notConfusing.getName() = m2.getName() + ) +select m1, + "It is confusing to have methods " + m1.getName() + " in " + m1.getDeclaringType().getName() + " and " + + m2.getName() + " in " + m2.getDeclaringType().getName() + "." diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/FieldMasksSuperField.java b/java/ql/src/Violations of Best Practice/Naming Conventions/FieldMasksSuperField.java new file mode 100644 index 00000000000..9909f06de27 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/FieldMasksSuperField.java @@ -0,0 +1,24 @@ +public class FieldMasksSuperField { + static class Person { + protected int age; + public Person(int age) + { + this.age = age; + } + } + + static class Employee extends Person { + protected int age; // This field hides 'Person.age'. + protected int numberOfYearsEmployed; + public Employee(int age, int numberOfYearsEmployed) + { + super(age); + this.numberOfYearsEmployed = numberOfYearsEmployed; + } + } + + public static void main(String[] args) { + Employee e = new Employee(20, 2); + System.out.println(e.age); + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/FieldMasksSuperField.qhelp b/java/ql/src/Violations of Best Practice/Naming Conventions/FieldMasksSuperField.qhelp new file mode 100644 index 00000000000..15a1acf843f --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/FieldMasksSuperField.qhelp @@ -0,0 +1,41 @@ + + + + + +

    A field that has the same name as a field in a superclass hides the field in the superclass. +Such hiding might be unintentional, especially if there are no references to the hidden field using +the super qualifier. In any case, it makes code more difficult to read.

    + +
    + + +

    Ensure that any hiding is intentional. For clarity, it may be better to rename the field in the +subclass.

    + +
    + +

    In the following example, the programmer unintentionally added an age field to Employee, which +hides the age field in Person. The constructor in Person sets the age + field in Person to 20 but the age field in Employee +is still 0. This means that the program outputs 0, which is probably not what was intended.

    + +

    To fix this, delete the declaration of age on line 11.

    +
    + + + +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • +
  • +The Java Tutorials: +Hiding Fields. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/FieldMasksSuperField.ql b/java/ql/src/Violations of Best Practice/Naming Conventions/FieldMasksSuperField.ql new file mode 100644 index 00000000000..37d745a3410 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/FieldMasksSuperField.ql @@ -0,0 +1,35 @@ +/** + * @name Field masks field in super class + * @description Hiding a field in a superclass by redeclaring it in a subclass might be + * unintentional, especially if references to the hidden field are not qualified using + * 'super'. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/field-masks-super-field + * @tags maintainability + * readability + */ +import java + +class VisibleInstanceField extends Field { + VisibleInstanceField() { + not this.isPrivate() and + not this.isStatic() + } +} + +from RefType type, RefType supertype, + VisibleInstanceField masked, VisibleInstanceField masking +where + type.getASourceSupertype+() = supertype and + masking.getDeclaringType() = type and + masked.getDeclaringType() = supertype and + masked.getName() = masking.getName() and + // Exclude intentional masking. + not exists(VarAccess va | va.getVariable() = masked | + va.getQualifier() instanceof SuperAccess + ) and + type.fromSource() +select masking, "This field shadows another field called $@ in a superclass.", + masked, masked.getName() diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsField.qhelp b/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsField.qhelp new file mode 100644 index 00000000000..a4b9cbea1a1 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsField.qhelp @@ -0,0 +1,23 @@ + + + + + +

    This query finds local variables that shadow like-named field declarations. This is confusing since it might easily lead to assignments +to the local variable that should have been to the corresponding field.

    + +
    +
    +

    For clarity, it may be better to rename the variable to avoid shadowing.

    + + + +
    + + + + + +
    diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsField.ql b/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsField.ql new file mode 100644 index 00000000000..bea931366cb --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsField.ql @@ -0,0 +1,23 @@ +/** + * @name Local variable shadows field + * @description If a local variable shadows a field of the same name, each use of + * the name is harder to read. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/local-shadows-field-unused + * @tags maintainability + */ +import java +import Shadowing + +from LocalVariableDecl d, Class c, Field f, Callable callable, string callableType +where + shadows(d, c, f, callable) and + not assignmentToShadowingLocal(d, f) and + not assignmentFromShadowingLocal(d, f) and + not thisAccess(d, f) and not confusingAccess(d, f) and + (if callable instanceof Constructor then callableType = "" else callableType = "method ") +select + d, "This local variable shadows field $@, which is not used in " + callableType + "$@.", + f, f.getName(), callable, callable.getName() diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.java b/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.java new file mode 100644 index 00000000000..6dac73cc35b --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.java @@ -0,0 +1,15 @@ +public class Container +{ + private int[] values; // Field called 'values' + + public Container (int... values) { + this.values = values; + } + + public Container dup() { + int length = values.length; + int[] values = new int[length]; // Local variable called 'values' + Container result = new Container(values); + return result; + } +} diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.qhelp b/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.qhelp new file mode 100644 index 00000000000..29d419f72e1 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.qhelp @@ -0,0 +1,41 @@ + + + + + +

    If a method declares a local variable with the same name as a field, +then it is very easy to mix up the two when reading or modifying +the program.

    + +
    + + +

    Consider using different names for the field and local variable to make the difference +between them clear.

    + +
    + + +

    The following example shows a local variable values that has the same name as a field.

    + + + +
    + + + +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • +
  • +Java Language Specification: + +6.4 Shadowing and Obscuring. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.ql b/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.ql new file mode 100644 index 00000000000..75b99c9ae4b --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.ql @@ -0,0 +1,32 @@ +/** + * @name Possible confusion of local and field + * @description A method in which a variable is declared with the same name as a field is difficult + * to understand. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/local-shadows-field + * @tags maintainability + * readability + */ +import java +import Shadowing + +from LocalVariableDecl d, Class c, Field f, Callable callable, string callableType, string message +where + shadows(d, c, f, callable) and + not assignmentToShadowingLocal(d, f) and + not assignmentFromShadowingLocal(d, f) and + (if callable instanceof Constructor + then callableType = "" + else callableType = "method ") and + ( + confusingAccess(d, f) and + message = "Confusing name: " + callableType + + "$@ also refers to field $@ (without qualifying it with 'this')." + or + thisAccess(d, f) and not confusingAccess(d, f) and + message = "Potentially confusing name: " + callableType + + "$@ also refers to field $@ (as this." + f.getName() + ")." + ) +select d, message, callable, callable.getName(), f, f.getName() diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.java b/java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.java new file mode 100644 index 00000000000..e2c5a976e86 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.java @@ -0,0 +1,16 @@ +import com.company.util.Attendees; + +public class Meeting +{ + private Attendees attendees; + + // ... + // Many lines + // ... + + // AVOID: This class has the same name as its superclass. + private static class Attendees extends com.company.util.Attendees + { + // ... + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.qhelp b/java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.qhelp new file mode 100644 index 00000000000..580f88cb6c6 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.qhelp @@ -0,0 +1,38 @@ + + + + + +

    A class that has the same name as its superclass may be confusing.

    + +
    + + +

    Clarify the difference between the subclass and the superclass by +using different names.

    + +
    + + +

    In the following example, it is not clear that the attendees field refers to the +inner class Attendees and not the class com.company.util.Attendees.

    + + + +

    To fix this, the inner class should be renamed.

    + +
    + + + +
  • +R. C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship, §17.N4. Prentice +Hall, 2008. +
  • + + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.ql b/java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.ql new file mode 100644 index 00000000000..4ac08aeb8ca --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.ql @@ -0,0 +1,21 @@ +/** + * @name Class has same name as super class + * @description A class that has the same name as its superclass may be confusing. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/class-name-matches-super-class + * @tags maintainability + * readability + * naming + */ +import java + +from RefType sub, RefType sup +where + sub.fromSource() and + sup = sub.getASupertype() and + sub.getName() = sup.getName() +select + sub, sub.getName() + " has the same name as its supertype $@.", + sup, sup.getQualifiedName() diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/Shadowing.qll b/java/ql/src/Violations of Best Practice/Naming Conventions/Shadowing.qll new file mode 100644 index 00000000000..7488ac84590 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/Shadowing.qll @@ -0,0 +1,83 @@ +import java + +predicate initializedToField(LocalVariableDecl d, Field f) { + exists(LocalVariableDeclExpr e | e.getVariable() = d and f.getAnAccess() = e.getInit()) +} + +predicate getterFor(Method m, Field f) { + m.getName().matches("get%") and + m.getDeclaringType() = f.getDeclaringType() and + exists(ReturnStmt ret | ret.getEnclosingCallable() = m and ret.getResult() = f.getAnAccess()) +} + +predicate setterFor(Method m, Field f) { + m.getName().matches("set%") and + m.getDeclaringType() = f.getDeclaringType() and + f.getAnAssignedValue() = m.getAParameter().getAnAccess() and + m.getNumberOfParameters() = 1 +} + +predicate shadows(LocalVariableDecl d, Class c, Field f, Callable method) { + d.getCallable() = method and + method.getDeclaringType() = c and + c.getAField() = f and + f.getName() = d.getName() and + f.getType() = d.getType() and + not d.getCallable().isStatic() and + not f.isStatic() +} + +predicate thisAccess(LocalVariableDecl d, Field f) { + shadows(d, _, f, _) and + exists(VarAccess va | va.getVariable().(Field).getSourceDeclaration() = f | + va.getQualifier() instanceof ThisAccess and + va.getEnclosingCallable() = d.getCallable() + ) +} + +predicate confusingAccess(LocalVariableDecl d, Field f) { + shadows(d, _, f, _) and + exists(VarAccess va | va.getVariable().(Field).getSourceDeclaration() = f | + not exists(va.getQualifier()) and + va.getEnclosingCallable() = d.getCallable() + ) +} + +predicate assignmentToShadowingLocal(LocalVariableDecl d, Field f) { + shadows(d, _, _, _) and + exists(Expr assignedValue, Expr use | + d.getAnAssignedValue() = assignedValue and getARelevantChild(assignedValue) = use + | + exists(FieldAccess access, Field ff | access = assignedValue | + ff = access.getField() and + ff.getSourceDeclaration() = f + ) or + exists(MethodAccess get, Method getter | get = assignedValue and getter = get.getMethod() | + getterFor(getter, f) + ) + ) +} + +predicate assignmentFromShadowingLocal(LocalVariableDecl d, Field f) { + shadows(d, _, _, _) and + exists(VarAccess access | access = d.getAnAccess() | + exists(MethodAccess set, Expr arg, Method setter | + access = getARelevantChild(arg) and + arg = set.getAnArgument() and + setter = set.getMethod() and + setterFor(setter, f) + ) or + exists(Field instance, Expr assignedValue | + access = getARelevantChild(assignedValue) and + assignedValue = instance.getAnAssignedValue() and + instance.getSourceDeclaration() = f + ) + ) +} + +private +Expr getARelevantChild(Expr parent) { + exists(MethodAccess ma | parent = ma.getAnArgument() and result = parent) or + exists(Variable v | parent = v.getAnAccess() and result = parent) or + exists(Expr mid | mid = getARelevantChild(parent) and result = mid.getAChildExpr()) +} diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.java b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.java new file mode 100644 index 00000000000..06655aa8c1f --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.java @@ -0,0 +1,8 @@ +void main() { + // ... + // BAD: Call to 'runFinalizersOnExit' forces execution of all finalizers on termination of + // the runtime, which can cause live objects to transition to an invalid state. + // Avoid using this method (and finalizers in general). + System.runFinalizersOnExit(true); + // ... +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.qhelp b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.qhelp new file mode 100644 index 00000000000..0b5ea2c768a --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.qhelp @@ -0,0 +1,72 @@ + + + + + +

    +Avoid calling System.runFinalizersOnExit or Runtime.runFinalizersOnExit, +which are considered to be dangerous methods. +

    + +

    +The Java Development Kit documentation for System.runFinalizersOnExit states: +

    + +

    +This method is inherently unsafe. It may result in finalizers being called on live objects while other threads are concurrently manipulating those objects, +resulting in erratic behavior or deadlock. +

    +
    + +

    +Object finalizers are normally only called when the object is about to be collected by the garbage collector. Using runFinalizersOnExit sets a Java Virtual Machine-wide +flag that executes finalizers on all objects with a finalize method before the runtime exits. This would require all objects with finalizers +to defend against the possibility of finalize being called when the object is still in use, which is not practical for most +applications. +

    + +
    + +

    +Ensure that the code does not rely on the execution of finalizers. If the code is dependent on the garbage collection behavior of the Java Virtual Machine, +there is no guarantee that finalizers will be executed in a timely manner, or at all. This may become a problem if finalizers are used to +dispose of limited system resources, such as file handles. +

    +

    +Instead of finalizers, use explicit dispose methods in finally blocks, to make sure that an object's resources are released. +

    + +
    + + +

    The following example shows a program that calls runFinalizersOnExit, which is not +recommended.

    + + + +

    The following example shows the recommended approach: a program that calls a dispose method in a finally block.

    + + + +
    + + + +
  • + J. Bloch, Effective Java (second edition), Item 7. Addison-Wesley, 2008. +
  • +
  • + Java 6 API Documentation: + System.runFinalizersOnExit(), + Object.finalize(). +
  • +
  • + Java SE Documentation: + Java Thread Primitive Deprecation. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.ql b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.ql new file mode 100644 index 00000000000..53d484bec6a --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.ql @@ -0,0 +1,25 @@ +/** + * @name Dangerous runFinalizersOnExit + * @description Calling 'System.runFinalizersOnExit' or 'Runtime.runFinalizersOnExit' + * may cause finalizers to be run on live objects, leading to erratic behavior or + * deadlock. + * @kind problem + * @problem.severity error + * @precision medium + * @id java/run-finalizers-on-exit + * @tags reliability + * maintainability + */ +import java + +from MethodAccess ma, Method runfinalizers, Class c +where + ma.getMethod() = runfinalizers and + runfinalizers.hasName("runFinalizersOnExit") and + runfinalizers.getDeclaringType() = c and + ( + c.hasName("Runtime") or + c.hasName("System") + ) and + c.getPackage().hasName("java.lang") +select ma, "Call to runFinalizersOnExit." diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExitGood.java b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExitGood.java new file mode 100644 index 00000000000..8cda5c7ab03 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExitGood.java @@ -0,0 +1,23 @@ +// Instead of using finalizers, define explicit termination methods +// and call them in 'finally' blocks. +class LocalCache { + private Collection cacheFiles = ...; + + // Explicit method to close all cacheFiles + public void dispose() { + for (File cacheFile : cacheFiles) { + disposeCacheFile(cacheFile); + } + } +} + +void main() { + LocalCache cache = new LocalCache(); + try { + // Use the cache + } finally { + // Call the termination method in a 'finally' block, to ensure that + // the cache's resources are freed. + cache.dispose(); + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.java b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.java new file mode 100644 index 00000000000..2fb4df11283 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.java @@ -0,0 +1,9 @@ +public static void main(String args[]) { + String name = "John Doe"; + + // BAD: Unnecessary call to 'toString' on 'name' + System.out.println("Hi, my name is " + name.toString()); + + // GOOD: No call to 'toString' on 'name' + System.out.println("Hi, my name is " + name); +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.qhelp b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.qhelp new file mode 100644 index 00000000000..410f2387a9c --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.qhelp @@ -0,0 +1,42 @@ + + + + + +

    +There is no need to call toString on a String because it just returns the object itself. From the Java API +Specification entry for String.toString(): +

    +

    +public String toString()

    +This object (which is already a string!) is itself returned. +

    +
    + +
    + +

    +Do not call toString on a String object. +

    + +
    + + +

    The following example shows an unnecessary call to toString on the string name.

    + + + +
    + + + +
  • + Java 6 API Specification: + String.toString(). +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.ql b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.ql new file mode 100644 index 00000000000..dda46d4f30d --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.ql @@ -0,0 +1,17 @@ +/** + * @name Useless toString on String + * @description Calling 'toString' on a string is redundant. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/useless-tostring-call + * @tags maintainability + */ +import java + +from MethodAccess ma, Method tostring +where + tostring.hasName("toString") and + tostring.getDeclaringType() instanceof TypeString and + ma.getMethod() = tostring +select ma, "Redundant call to 'toString' on a String object." diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.java b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.java new file mode 100644 index 00000000000..da7277aa25c --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.java @@ -0,0 +1,24 @@ +// Problem 1: Miss out cleanup code +class FileOutput { + boolean write(String[] s) { + try { + output.write(s.getBytes()); + } catch (IOException e) { + System.exit(1); + } + return true; + } +} + +// Problem 2: Make code reuse difficult +class Action { + public void run() { + // ... + // Perform tasks ... + // ... + System.exit(0); + } + public static void main(String[] args) { + new Action(args).run(); + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.qhelp b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.qhelp new file mode 100644 index 00000000000..bb9b2b47e17 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.qhelp @@ -0,0 +1,66 @@ + + + +

    Calling one of the methods System.exit, Runtime.halt, and Runtime.exit +immediately terminates the Java Virtual Machine (JVM), +effectively killing all threads without giving any of them a chance to +perform cleanup actions or recover. As such, it is a dangerous thing to +do: firstly, it can terminate the entire program inadvertently, and +secondly, it can prevent important resources from being released or +program state from being written to disk consistently.

    + +

    It is sometimes considered acceptable to call System.exit +from a program's main method in order to indicate the overall exit status +of the program. Such calls are an exception to this rule.

    + +
    + + +

    It is usually preferable to use a different mechanism for reporting +failure conditions. Consider returning a special value (perhaps +null) that users of the current method check for and +recover from appropriately. Alternatively, throw a suitable exception, which +unwinds the stack and allows properly written code to clean up after itself, +while leaving other threads undisturbed.

    + +
    + + +

    In the following example, problem 1 shows that FileOutput.write tries +to write some data to disk and terminates the JVM if this fails. This +leaves the partially-written file on disk without any cleanup +code running. It would be better to either return false to +indicate the failure, or let the IOException propagate +upwards and be handled by a method that knows how to recover.

    + +

    Problem 2 is more subtle. In this example, there is just one entry point to +the program (the main method), which constructs an +Action and performs it. Action.run calls +System.exit to indicate successful completion. Consider, +however, how this code might be integrated in an application server that +constructs Action instances and calls +run on them without going through main. +The fact that run terminates the JVM instead of returning its +exit code as an integer makes that use-case impossible.

    + + + +
    + + + +
  • + J. Bloch, + Effective Java (second edition), p. 232. + Addison-Wesley, 2008. +
  • +
  • Java Platform, Standard Edition 7, API Specification: +System.exit(int), +Runtime.halt(int), +Runtime.exit(int). +
  • + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.ql b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.ql new file mode 100644 index 00000000000..0921436ddcf --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.ql @@ -0,0 +1,27 @@ +/** + * @name Forcible JVM termination + * @description Calling 'System.exit', 'Runtime.halt', or 'Runtime.exit' may make code harder to + * reuse and prevent important cleanup steps from running. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/jvm-exit + * @tags reliability + * maintainability + * external/cwe/cwe-382 + */ +import java + +from Method m, MethodAccess sysexitCall, Method sysexit, Class system +where + sysexitCall = m.getACallSite(sysexit) and + (sysexit.hasName("exit") or sysexit.hasName("halt")) and + sysexit.getDeclaringType() = system and + ( system.hasQualifiedName("java.lang", "System") or + system.hasQualifiedName("java.lang", "Runtime") + ) and + m.fromSource() and + not m instanceof MainMethod +select sysexitCall, + "Avoid calls to " + sysexit.getDeclaringType().getName() + "." + sysexit.getName() + + "() as this makes code harder to reuse." diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.java b/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.java new file mode 100644 index 00000000000..2cff927b157 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.java @@ -0,0 +1,21 @@ +// This class does not have a 'toString' method, so 'java.lang.Object.toString' +// is used when the class is converted to a string. +class WrongPerson { + private String name; + private Date birthDate; + + public WrongPerson(String name, Date birthDate) { + this.name =name; + this.birthDate = birthDate; + } +} + +public static void main(String args[]) throws Exception { + DateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd"); + WrongPerson wp = new WrongPerson("Robert Van Winkle", dateFormatter.parse("1967-10-31")); + + // BAD: The following statement implicitly calls 'Object.toString', + // which returns something similar to: + // WrongPerson@4383f74d + System.out.println(wp); +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.qhelp b/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.qhelp new file mode 100644 index 00000000000..d2a9a457107 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.qhelp @@ -0,0 +1,54 @@ + + + + + +

    +In most cases, calling the default implementation of toString in java.lang.Object is not what is intended when +a string representation of an object is required. The output of the default toString method consists of the class name of the object +as well as the object's hashcode, which is usually not what was intended. +

    +

    +This rule includes explicit and implicit calls to toString that resolve to java.lang.Object.toString, particularly +calls that are used in print or log statements. +

    + +
    + +

    +For objects that are printed, define a toString method for the object that returns a human-readable string. +

    + +
    + + +

    The following example shows that printing an object makes an implicit call to toString. +Because the class WrongPerson does not have a toString method, +Object.toString is called instead, which returns the class name and the wp object's hashcode.

    + + + +

    In contrast, in the following modification of the example, the class Person does have a toString method, which returns a +string containing the arguments that were passed when the object p was created.

    + + + +
    + + + +
  • + J. Bloch, Effective Java (second edition), Item 10. Addison-Wesley, 2008. +
  • +
  • + Java 6 API Specification: Object.toString(). +
  • +
  • + Java Language Specification, 3rd ed: 5.4 String Conversion. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.ql b/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.ql new file mode 100644 index 00000000000..826fc9df913 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.ql @@ -0,0 +1,62 @@ +/** + * @name Use of default toString() + * @description Calling the default implementation of 'toString' returns a value that is unlikely to + * be what you expect. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/call-to-object-tostring + * @tags reliability + * maintainability + */ +import java +import semmle.code.java.StringFormat + +predicate explicitToStringCall(Expr e) { + exists(MethodAccess ma, Method toString | toString = ma.getMethod() | + e = ma.getQualifier() and + toString.getName() = "toString" and + toString.getNumberOfParameters() = 0 and + not toString.isStatic() + ) +} + +predicate directlyDeclaresToString(Class c) { + exists(Method m | m.getDeclaringType() = c | + m.getName() = "toString" and + m.getNumberOfParameters() = 0 + ) +} + +predicate inheritsObjectToString(Class t) { + not directlyDeclaresToString(t.getSourceDeclaration()) and + ( + t.getASupertype().hasQualifiedName("java.lang", "Object") + or + not t.getASupertype().hasQualifiedName("java.lang", "Object") and + inheritsObjectToString(t.getASupertype()) + ) +} + +Class getAnImplementation(RefType parent) { + result = parent.getASubtype*() and + not result.isAbstract() +} + +predicate bad(RefType t) { + forex(Class sub | sub = getAnImplementation(t) | inheritsObjectToString(sub)) and + not t instanceof Array and + not t instanceof GenericType and + not t instanceof BoundedType and + t.fromSource() +} + +from Expr e, RefType sourceType +where + (implicitToStringCall(e) or explicitToStringCall(e)) and + sourceType = e.getType().(RefType).getSourceDeclaration() and + bad(sourceType) and + not sourceType.isAbstract() and + sourceType.fromSource() +select e, "Default toString(): " + e.getType().getName() + + " inherits toString() from Object, and so is not suitable for printing." diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToStringGood.java b/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToStringGood.java new file mode 100644 index 00000000000..32b6b70e209 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToStringGood.java @@ -0,0 +1,26 @@ +// This class does have a 'toString' method, which is used when the object is +// converted to a string. +class Person { + private String name; + private Date birthDate; + + public String toString() { + DateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd"); + return "(Name: " + name + ", Birthdate: " + dateFormatter.format(birthDate) + ")"; + } + + public Person(String name, Date birthDate) { + this.name =name; + this.birthDate = birthDate; + } +} + +public static void main(String args[]) throws Exception { + DateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd"); + Person p = new Person("Eric Arthur Blair", dateFormatter.parse("1903-06-25")); + + // GOOD: The following statement implicitly calls 'Person.toString', + // which correctly returns a human-readable string: + // (Name: Eric Arthur Blair, Birthdate: 1903-06-25) + System.out.println(p); +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.java b/java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.java new file mode 100644 index 00000000000..47c881dbf20 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.java @@ -0,0 +1,15 @@ +class RequestHandler extends Thread { + private boolean isRunning; + private Connection conn = new Connection(); + + public void run() { + while (isRunning) { + Request req = conn.getRequest(); + // Process the request ... + + System.gc(); // This call may cause a garbage collection after each request. + // This will likely reduce the throughput of the RequestHandler + // because the JVM spends time on unnecessary garbage collection passes. + } + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.qhelp b/java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.qhelp new file mode 100644 index 00000000000..c65c984286c --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.qhelp @@ -0,0 +1,50 @@ + + + + + +

    +You should avoid making calls to explicit garbage collection methods (Runtime.gc and System.gc). The calls are not +guaranteed to trigger garbage collection, and they may also trigger unnecessary garbage collection passes that lead to decreased +performance. +

    + +
    + + +

    It is better to let the Java Virtual Machine (JVM) handle garbage collection. If it becomes necessary to control how the JVM handles memory, it is better to use the +JVM's memory and garbage collection options (for example, -Xmx, -XX:NewRatio, -XX:Use*GC) than to trigger +garbage collection in the application.

    + +

    The memory management classes that are used by Real-Time Java are an exception to this rule, +because they are designed to handle garbage collection differently from the JVM default.

    + +
    + + +

    The following example shows code that makes connection requests, and tries to trigger garbage +collection after it has processed each request.

    + + + +

    It is better to remove the call to System.gc and rely on the JVM to dispose of the +connection.

    + +
    + + + +
  • + Java 6 API Documentation: + System.gc(). +
  • +
  • + Oracle Technology Network: + Java SE 6 HotSpot Virtual Machine Garbage Collection Tuning. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.ql b/java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.ql new file mode 100644 index 00000000000..f48e6d00b1e --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.ql @@ -0,0 +1,22 @@ +/** + * @name Explicit garbage collection + * @description Triggering garbage collection explicitly may either have no effect or may trigger + * unnecessary garbage collection. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/garbage-collection + * @tags reliability + * maintainability + */ +import java + +from MethodAccess mc, Method m +where + ( + m.getDeclaringType().hasQualifiedName("java.lang", "Runtime") or + m.getDeclaringType().hasQualifiedName("java.lang", "System") + ) and + m.hasName("gc") and + mc.getMethod() = m +select mc, "Explicit garbage collection. This should only be used in benchmarking code." diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/NextFromIterator.java b/java/ql/src/Violations of Best Practice/Undesirable Calls/NextFromIterator.java new file mode 100644 index 00000000000..14bbaf446ad --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/NextFromIterator.java @@ -0,0 +1,26 @@ +public class NextFromIterator implements Iterator { + private int position = -1; + private List list = new ArrayList() {{ + add("alpha"); add("bravo"); add("charlie"); add("delta"); add("echo"); add("foxtrot"); + }}; + + public boolean hasNext() { + return next() != null; // BAD: Call to 'next' + } + + public String next() { + position++; + return position < list.size() ? list.get(position) : null; + } + + public void remove() { + // ... + } + + public static void main(String[] args) { + NextFromIterator x = new NextFromIterator(); + while(x.hasNext()) { + System.out.println(x.next()); + } + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/NextFromIterator.qhelp b/java/ql/src/Violations of Best Practice/Undesirable Calls/NextFromIterator.qhelp new file mode 100644 index 00000000000..a86432566e8 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/NextFromIterator.qhelp @@ -0,0 +1,49 @@ + + + + + +

    Iterator implementations with a hasNext method that +calls the next method are most likely incorrect. This is because next +changes the iterator's position to the next element and returns that element, which is unlikely to +be desirable in the implementation of hasNext.

    + +
    + + +

    Ensure that any calls to next from +within hasNext are legitimate. +The hasNext method should indicate whether there are +further elements remaining in the iteration without changing the iterator's state by calling +next. +

    + +
    + + +

    In the following example, which outputs the contents of a string, hasNext calls +next, which has the effect of changing the iterator's position. Given that +main also calls next when it outputs an item, some items are skipped and +only half the items are output.

    + + + +

    Instead, the implementation of hasNext should use another way of indicating whether +there are further elements in the string without calling next. For example, hasNext +could check the underlying array directly to see if there is an element at the next position.

    + +
    + + + +
  • + Java API Documentation: + Iterator.hasNext(), + Iterator.next(). +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/NextFromIterator.ql b/java/ql/src/Violations of Best Practice/Undesirable Calls/NextFromIterator.ql new file mode 100644 index 00000000000..1bc74ae7724 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/NextFromIterator.ql @@ -0,0 +1,28 @@ +/** + * @name Next in hasNext implementation + * @description Iterator implementations whose 'hasNext' method calls 'next' are most likely + * incorrect. + * @kind problem + * @problem.severity warning + * @precision medium + * @id java/iterator-hasnext-calls-next + * @tags reliability + * correctness + */ +import java + +from MethodAccess m +where + m.getMethod().hasName("next") and + m.getMethod().getNumberOfParameters() = 0 and + ( + not m.hasQualifier() or + m.getQualifier() instanceof ThisAccess + ) and + exists(Interface i, Method hasNext | + i.getSourceDeclaration().hasQualifiedName("java.util", "Iterator") and + m.getEnclosingCallable() = hasNext and + hasNext.getDeclaringType().getSourceDeclaration().getASupertype*() = i and + hasNext.hasName("hasNext") + ) +select m, "next() called from within an Iterator method." diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.java b/java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.java new file mode 100644 index 00000000000..78f1221829a --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.java @@ -0,0 +1,24 @@ +public static void main(String args[]) { + String[] words = {"Who", "is", "John", "Galt"}; + String[][] wordMatrix = {{"There", "is"}, {"no", "spoon"}}; + + // BAD: This implicitly uses 'Object.toString' to convert the contents + // of 'words[]', and prints out something similar to: + // [Ljava.lang.String;@459189e1 + System.out.println(words); + + // GOOD: 'Arrays.toString' calls 'toString' on + // each of the array's elements. The statement prints out: + // [Who, is, John, Galt] + System.out.println(Arrays.toString(words)); + + // ALMOST RIGHT: This calls 'toString' on each of the multi-dimensional + // array's elements. However, because the elements are arrays, the statement + // prints out something similar to: + // [[Ljava.lang.String;@55f33675, [Ljava.lang.String;@527c6768]] + System.out.println(Arrays.toString(wordMatrix)); + + // GOOD: This properly prints out the contents of the multi-dimensional array: + // [[There, is], [no, spoon]] + System.out.println(Arrays.deepToString(wordMatrix)); +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.qhelp b/java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.qhelp new file mode 100644 index 00000000000..f8a6dabfe2c --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.qhelp @@ -0,0 +1,49 @@ + + + + + +

    +Printing an array is likely to produce unintended results. That is, the result does not contain the contents of the array. +This is because the array is implicitly converted to a String using Object.toString, which just returns the following value: +

    +

    + +getClass().getName() + '@' + Integer.toHexString(hashCode()) + +

    + +
    + +

    +When converting an array to a readable string, use Arrays.toString for one-dimensional arrays, or +Arrays.deepToString for multi-dimensional arrays. These functions iterate over the contents of the array +and produce human-readable output. +

    + +
    + + +

    In the following example, the contents of the array words are printed out only if +Arrays.toString is called on the array first. Similarly, the contents of the multi-dimensional +array wordMatrix are printed out only if Arrays.deepToString is called +on the array first.

    + + + +
    + + + +
  • +Java 6 API Documentation: +Arrays.toString(), +Arrays.deepToString(), +Object.toString(). +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.ql b/java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.ql new file mode 100644 index 00000000000..077de8b19e6 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.ql @@ -0,0 +1,32 @@ +/** + * @name Implicit conversion from array to string + * @description Directly printing an array, without first converting the array to a string, + * produces unreadable results. + * @kind problem + * @problem.severity recommendation + * @precision very-high + * @id java/print-array + * @tags maintainability + */ +import java +import semmle.code.java.StringFormat + +/** + * Holds if `e` is an argument of `Arrays.toString(..)`. + */ +predicate arraysToStringArgument(Expr e) { + exists(MethodAccess ma, Method m | + ma.getAnArgument() = e and + ma.getMethod() = m and + m.getDeclaringType().hasQualifiedName("java.util", "Arrays") and + m.hasName("toString") + ) +} +from Expr arr +where + arr.getType() instanceof Array and + implicitToStringCall(arr) + or + arr.getType().(Array).getComponentType() instanceof Array and + arraysToStringArgument(arr) +select arr, "Implicit conversion from Array to String." diff --git a/java/ql/src/Violations of Best Practice/legacy/AutoBoxing.java b/java/ql/src/Violations of Best Practice/legacy/AutoBoxing.java new file mode 100644 index 00000000000..a15bff3d039 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/legacy/AutoBoxing.java @@ -0,0 +1,4 @@ +Long sum = 0L; +for (long k = 0; k < Integer.MAX_VALUE; k++) { + sum += k; // AVOID: Inefficient unboxing and reboxing of 'sum' +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/legacy/AutoBoxing.qhelp b/java/ql/src/Violations of Best Practice/legacy/AutoBoxing.qhelp new file mode 100644 index 00000000000..4d826204168 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/legacy/AutoBoxing.qhelp @@ -0,0 +1,72 @@ + + + + + +

    For each primitive type, such as int or double, +there is a corresponding boxed reference type, such as Integer or +Double. These boxed versions differ from their primitive equivalents +because they can hold an undefined null element in addition to numeric (or other) values, +and there can be more than one instance of a boxed type representing the same value. +

    + +

    In Java 5 and later, automated boxing and unboxing conversions have been added to the language. +Although these automated conversions reduce the verbosity of the code, they can hide potential problems. +Such problems include performance issues because of unnecessary object creation, and confusion of boxed +types with their primitive equivalents. +

    + +
    + + +

    +Generally, you should use primitive types (boolean, byte, char, short, int, long, float, double) +in preference to boxed types (Boolean, Byte, Character, Short, Integer, Long, Float, +Double), whenever there is a choice. Exceptions are when a primitive value is used in +collections and other parameterized types, or +when a null value is explicitly used to represent an undefined value. +

    +

    Where they cannot be avoided, perform boxing and unboxing conversions explicitly to avoid +possible confusion of boxed types and their primitive equivalents. In cases where boxing conversions cause +performance issues, use primitive types instead. +

    + +
    + + +

    In the following example, declaring the variable sum to have boxed type +Long causes it to be unboxed and reboxed during execution of the statement inside the +loop.

    + + + +

    To avoid this inefficiency, declare sum to have primitive type long +instead.

    + +
    + + + +
  • + J. Bloch, Effective Java (second edition), + Item 49. + Addison-Wesley, 2008. +
  • +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • +
  • + Java Language Specification: + 5.1.7 Boxing Conversion. +
  • +
  • + Java SE Documentation: + Autoboxing. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/legacy/AutoBoxing.ql b/java/ql/src/Violations of Best Practice/legacy/AutoBoxing.ql new file mode 100644 index 00000000000..5cb5a443e24 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/legacy/AutoBoxing.ql @@ -0,0 +1,93 @@ +/** + * @name Auto boxing or unboxing + * @description Implicit boxing or unboxing of primitive types, such as 'int' and 'double', + * may cause confusion and subtle performance problems. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/implicit-auto-boxing + * @tags efficiency + */ + +import java + +/** An expression of primitive type. */ +class PrimitiveExpr extends Expr { + PrimitiveExpr() { + this.getType() instanceof PrimitiveType + } +} + +/** An expression of boxed type. */ +class BoxedExpr extends Expr { + BoxedExpr() { + this.getType() instanceof BoxedType + } +} + +/** + * Relate expressions and the variables they flow into in one step, + * either by assignment or parameter passing. + */ +Variable flowTarget(Expr arg) { + arg = result.getAnAssignedValue() or + exists(Call c, int i | + c.getArgument(i) = arg and result = c.getCallee().getParameter(i) + ) +} + +/** + * Holds if `e` is in a syntactic position where it is implicitly unboxed. + */ +predicate unboxed(BoxedExpr e) { + exists(BinaryExpr bin | e = bin.getAnOperand() | + if (bin instanceof EqualityTest or bin instanceof ComparisonExpr) then + bin.getAnOperand() instanceof PrimitiveExpr + else + bin instanceof PrimitiveExpr + ) + or + exists(Assignment assign | assign.getDest() instanceof PrimitiveExpr | + assign.getSource() = e + ) + or + flowTarget(e).getType() instanceof PrimitiveType + or + exists(ConditionalExpr cond | cond instanceof PrimitiveExpr | + cond.getTrueExpr() = e or cond.getFalseExpr() = e + ) +} + +/** + * Holds if `e` is in a syntactic position where it is implicitly boxed. + */ +predicate boxed(PrimitiveExpr e) { + exists(AssignExpr assign | assign.getDest() instanceof BoxedExpr | + assign.getSource() = e) + or + flowTarget(e).getType() instanceof BoxedType + or + exists(ConditionalExpr cond | cond instanceof BoxedExpr | + cond.getTrueExpr() = e or cond.getFalseExpr() = e + ) +} + +/** + * Holds if `e` is an assignment that unboxes, updates and reboxes `v`. + */ +predicate rebox(Assignment e, Variable v) { + v.getType() instanceof BoxedType and + not e instanceof AssignExpr and + e.getDest() = v.getAnAccess() +} + +from Expr e, string conv +where + boxed(e) and conv = "This expression is implicitly boxed." + or + unboxed(e) and conv = "This expression is implicitly unboxed." + or + exists(Variable v | rebox(e, v) | + conv = "This expression implicitly unboxes, updates, and reboxes the value of '" + v.getName() + "'." + ) +select e, conv diff --git a/java/ql/src/Violations of Best Practice/legacy/FinallyMayNotComplete.qhelp b/java/ql/src/Violations of Best Practice/legacy/FinallyMayNotComplete.qhelp new file mode 100644 index 00000000000..647ab37a465 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/legacy/FinallyMayNotComplete.qhelp @@ -0,0 +1,50 @@ + + + + + +

    A finally block that does not complete normally +suppresses any exceptions that may have been thrown in the corresponding +try block. This can happen if the finally block +contains any return or throw statements, or if it +contains any break or continue statements whose +jump target lies outside of the finally block. +

    + +
    + + +

    To avoid suppressing exceptions that are thrown in a try block, +design the code so that the corresponding finally block +always completes normally. Remove any of the following statements that +may cause it to terminate abnormally: +

    + +
      +
    • return
    • +
    • throw
    • +
    • break
    • +
    • continue
    • +
    + +
    + + + +
  • + J. Bloch and N. Gafter, Java Puzzlers: Traps, Pitfalls, and Corner Cases, Puzzle 36. Addison-Wesley, 2005. +
  • +
  • + The Java Language Specification: + Execution of try-finally and try-catch-finally. +
  • +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/legacy/FinallyMayNotComplete.ql b/java/ql/src/Violations of Best Practice/legacy/FinallyMayNotComplete.ql new file mode 100644 index 00000000000..3e04d76e2be --- /dev/null +++ b/java/ql/src/Violations of Best Practice/legacy/FinallyMayNotComplete.ql @@ -0,0 +1,37 @@ +/** + * @name Finally block may not complete normally + * @description A 'finally' block that runs because an exception has been thrown, and that does not + * complete normally, causes the exception to disappear silently. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/abnormal-finally-completion + * @tags reliability + * correctness + * exceptions + * external/cwe/cwe-584 + */ + +import java + +Block finallyBlock() { + exists(TryStmt try | try.getFinally() = result) +} + +Stmt statementIn(Block finally) { + finallyBlock() = finally and + result.getParent+() = finally +} + +predicate banned(Stmt s, Block finally) { + s = statementIn(finally) and + ( + s instanceof ReturnStmt or + exists(ThrowStmt throw | s = throw and not throw.getLexicalCatchIfAny() = statementIn(finally)) or + exists(JumpStmt jump | s = jump and not jump.getTarget() = statementIn(finally)) + ) +} + +from Stmt s, Block finally +where banned(s, finally) +select s, "Leaving a finally-block with this statement can cause exceptions to silently disappear." diff --git a/java/ql/src/Violations of Best Practice/legacy/InexactVarArg.java b/java/ql/src/Violations of Best Practice/legacy/InexactVarArg.java new file mode 100644 index 00000000000..8402008007e --- /dev/null +++ b/java/ql/src/Violations of Best Practice/legacy/InexactVarArg.java @@ -0,0 +1,13 @@ +class InexactVarArg +{ + private static void length(Object... objects) { + System.out.println(objects.length); + } + + public static void main(String[] args) { + String[] words = { "apple", "banana", "cherry" }; + String[][] lists = { words, words }; + length(words); // BAD: Argument does not clarify + length(lists); // which parameter type is used. + } +} diff --git a/java/ql/src/Violations of Best Practice/legacy/InexactVarArg.qhelp b/java/ql/src/Violations of Best Practice/legacy/InexactVarArg.qhelp new file mode 100644 index 00000000000..c7c45cb0d4c --- /dev/null +++ b/java/ql/src/Violations of Best Practice/legacy/InexactVarArg.qhelp @@ -0,0 +1,81 @@ + + + + + +

    A variable arity method, commonly known as a varargs method, may be called +with different numbers of arguments. For example, the method sum(int... values) +may be called in all of the following ways:

    + +
      +
    • sum()
    • +
    • sum(1)
    • +
    • sum(1,2,3)
    • +
    • sum(new int[] { 1, 2, 3 })
    • +
    + +

    When a method foo(T... x) is called with an argument that is neither +T nor T[], but the argument can be cast as either, the choice of which +type the argument is cast as is compiler-dependent.

    + +
    + + +

    When a variable arity method, for example m(T... ts), is called with a +single argument (for example m(arg)), the type of the argument should be +either T or T[] (insert a cast if necessary).

    + +
    + + +

    In the following example, the calls to length do not pass an argument of the same +type as the parameter of length, which is Object or an array of +Object. Therefore, when the program is compiled with javac, the output is:

    + + +3 +2 + + +

    When the program is compiled with a different compiler, for example the default compiler for some +versions of Eclipse, the output may be:

    + + +3 +1 + + + + +

    To fix the code, length(words) should be replaced by either of the following:

    + +
      +
    • length((Object) words)
    • +
    • length((Object[]) words)
    • +
    + +

    Similarly, length(lists) should be replaced by one of the following:

    + +
      +
    • length((Object) lists)
    • +
    • length((Object[]) lists)
    • +
    + +
    + + + +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • +
  • +Java Language Specification: +8.4.1 Formal Parameters, +15.12.4.2 Evaluate Arguments.
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/legacy/InexactVarArg.ql b/java/ql/src/Violations of Best Practice/legacy/InexactVarArg.ql new file mode 100644 index 00000000000..1a14d9ed95b --- /dev/null +++ b/java/ql/src/Violations of Best Practice/legacy/InexactVarArg.ql @@ -0,0 +1,42 @@ +/** + * @name Inexact type match for varargs argument + * @description Calling a varargs method where it is unclear whether the arguments + * should be interpreted as a list of arguments or as a single argument, may lead + * to compiler-dependent behavior. + * @kind problem + * @problem.severity warning + * @precision low + * @id java/inexact-varargs + * @tags reliability + */ +import java + +predicate varArgsMethod(Method method, Array varargsType, int arity) { + exists(MethodAccess access | access.getMethod() = method and + arity = method.getNumberOfParameters() and + not access.getNumArgument() = arity and + method.getParameterType(arity-1) = varargsType + ) +} + +RefType normalised(Type type) { + type.(RawType).getErasure() = result or + type.(ParameterizedType).getErasure() = result or + type.(BoundedType).getUpperBoundType() = result or + (not type instanceof RawType and not type instanceof ParameterizedType and type = result) +} + +predicate equivalent(Array declared, Array used) { + normalised(declared.getElementType()) = normalised(used.getElementType()) and + declared.getDimension() = used.getDimension() +} + +from Method target, MethodAccess access, Array declaredType, Array usedType, int params +where + varArgsMethod(target, declaredType, params) and + target = access.getMethod() and + access.getNumArgument() = params and + usedType = access.getArgument(params - 1).getType() and + not equivalent(declaredType, usedType) and + declaredType.getDimension() != usedType.getDimension()+1 +select access.getArgument(params - 1), "Call to varargs method $@ with inexact argument type (compiler dependent).", target, target.getName() diff --git a/java/ql/src/Violations of Best Practice/legacy/ParameterAssignment.java b/java/ql/src/Violations of Best Practice/legacy/ParameterAssignment.java new file mode 100644 index 00000000000..a7f4ab31137 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/legacy/ParameterAssignment.java @@ -0,0 +1,18 @@ +final private static double KM_PER_MILE = 1.609344; + +// AVOID: Example that assigns to a parameter +public double milesToKM(double miles) { + miles *= KM_PER_MILE; + return miles; +} + +// GOOD: Example of using an expression instead +public double milesToKM(double miles) { + return miles * KM_PER_MILE; +} + +// GOOD: Example of using a local variable +public double milesToKM(double miles) { + double kilometres = miles * KM_PER_MILE; + return kilometres; +} diff --git a/java/ql/src/Violations of Best Practice/legacy/ParameterAssignment.qhelp b/java/ql/src/Violations of Best Practice/legacy/ParameterAssignment.qhelp new file mode 100644 index 00000000000..aaaf9688c3e --- /dev/null +++ b/java/ql/src/Violations of Best Practice/legacy/ParameterAssignment.qhelp @@ -0,0 +1,49 @@ + + + + + +

    +Programmers usually assume that the value of a parameter is the value that was +passed in to the method or constructor. Assigning a different value to a parameter in a +method or constructor invalidates that assumption.

    + +
    + + +

    Avoid assignment to parameters by doing one of the following:

    +
      +
    • Introduce a local variable and assign to that instead.
    • +
    • Use an expression directly rather than assigning it to a parameter.
    • +
    + +
    + + +

    +In the following example, the first method shows assignment to the parameter miles. +The second method shows how to avoid this by using the expression miles * KM_PER_MILE. +The third method shows how to avoid the assignment by declaring a local variable kilometres +and assigning to that. +

    + + + +
    + + + +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • +
  • +Java Basics: +Methods 4 - Local variables. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/legacy/ParameterAssignment.ql b/java/ql/src/Violations of Best Practice/legacy/ParameterAssignment.ql new file mode 100644 index 00000000000..4225776a53f --- /dev/null +++ b/java/ql/src/Violations of Best Practice/legacy/ParameterAssignment.ql @@ -0,0 +1,16 @@ +/** + * @name Assignment to parameter + * @description Changing a parameter's value in a method or constructor may decrease code + * readability. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/assignment-to-parameter + * @tags maintainability + */ + +import java + +from Assignment a, Parameter p +where a.getDest() = p.getAnAccess() +select a, "Assignment to parameters may decrease code readability." diff --git a/java/ql/src/Violations of Best Practice/legacy/UnnecessaryCast.java b/java/ql/src/Violations of Best Practice/legacy/UnnecessaryCast.java new file mode 100644 index 00000000000..b3b6764eba5 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/legacy/UnnecessaryCast.java @@ -0,0 +1,6 @@ +public class UnnecessaryCast { + public static void main(String[] args) { + Integer i = 23; + Integer j = (Integer)i; // AVOID: Redundant cast + } +} \ No newline at end of file diff --git a/java/ql/src/Violations of Best Practice/legacy/UnnecessaryCast.qhelp b/java/ql/src/Violations of Best Practice/legacy/UnnecessaryCast.qhelp new file mode 100644 index 00000000000..b6a631f226c --- /dev/null +++ b/java/ql/src/Violations of Best Practice/legacy/UnnecessaryCast.qhelp @@ -0,0 +1,38 @@ + + + + + +

    +A cast is unnecessary if the type of the operand is already the same as the type that is being cast to. +

    + +
    + + +

    +Avoid including unnecessary casts. +

    + +
    + +

    In the following example, casting i to an Integer is not necessary. It +is already an Integer.

    + + +

    To fix the code, delete (Integer) on the right-hand side of the assignment on line 4.

    + +
    + + + +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/legacy/UnnecessaryCast.ql b/java/ql/src/Violations of Best Practice/legacy/UnnecessaryCast.ql new file mode 100644 index 00000000000..2d760a374b3 --- /dev/null +++ b/java/ql/src/Violations of Best Practice/legacy/UnnecessaryCast.ql @@ -0,0 +1,18 @@ +/** + * @name Unnecessary cast + * @description Casting an object to its own type is unnecessary. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/redundant-cast + * @tags maintainability + * external/cwe/cwe-561 + */ + +import java + +from CastExpr redundant, Type type +where + redundant.getType() = type and + type = redundant.getExpr().getType() +select redundant, "This cast is redundant - the expression is already of type '" + type + "'." diff --git a/java/ql/src/Violations of Best Practice/legacy/UnnecessaryImport.qhelp b/java/ql/src/Violations of Best Practice/legacy/UnnecessaryImport.qhelp new file mode 100644 index 00000000000..58e8beefb8e --- /dev/null +++ b/java/ql/src/Violations of Best Practice/legacy/UnnecessaryImport.qhelp @@ -0,0 +1,35 @@ + + + +

    An import statement that is not necessary (because no part +of the file that it is in uses any imported type) should be avoided. Although +importing too many types does not affect performance, redundant import statements +introduce unnecessary and undesirable dependencies in the code. If an imported type is +renamed or deleted, the source code cannot be compiled because the +import statement cannot be resolved.

    + +

    Unnecessary import statements are often an indication of incomplete refactoring.

    + +
    + + +

    Avoid including an import statement that is not needed. Many modern IDEs have +automated support for doing this, typically under the +name 'Organize imports'. This sorts the import statements and +removes any that are not used, and it is good practice to run such a +command before every commit.

    + +
    + + + +
  • +Help - Eclipse Platform: +Java Compiler Errors/Warnings Preferences. +
  • + + +
    +
    diff --git a/java/ql/src/Violations of Best Practice/legacy/UnnecessaryImport.ql b/java/ql/src/Violations of Best Practice/legacy/UnnecessaryImport.ql new file mode 100644 index 00000000000..2be3595fcdf --- /dev/null +++ b/java/ql/src/Violations of Best Practice/legacy/UnnecessaryImport.ql @@ -0,0 +1,77 @@ +/** + * @name Unnecessary import + * @description A redundant 'import' statement introduces unnecessary and undesirable + * dependencies. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/unused-import + * @tags maintainability + * external/cwe/cwe-561 + */ + +import java + +string neededByJavadoc(JavadocElement c) { + result = c.getText().regexpCapture(".*\\{@link(?:plain)?\\s+(\\w+)\\b.*\\}.*", 1) or + result = c.(ThrowsTag).getExceptionName() or + result = c.(SeeTag).getReference() +} + +Annotation nestedAnnotation(Annotation a) { + result.getAnnotatedElement().(Expr).getParent+() = a +} + +RefType neededByAnnotation(Annotation a) { + exists(TypeAccess t | t.getParent+() = a | + result = t.getType().(RefType).getSourceDeclaration() + ) or + exists(ArrayTypeAccess at | at.getParent+() = a | + result = at.getType().(Array).getElementType().(RefType).getSourceDeclaration() + ) or + exists(VarAccess va | va.getParent+() = a | + result = va.getVariable().(Field).getDeclaringType() + ) or + result = a.getType() or + result = a.getType().(NestedType).getEnclosingType+() or + result = neededByAnnotation(nestedAnnotation(a)) +} + +RefType neededType(CompilationUnit cu) { + // Annotations + exists(Annotation a | a.getAnnotatedElement().getCompilationUnit() = cu | + result = neededByAnnotation(a) + ) or + // type accesses + exists(TypeAccess t | t.getCompilationUnit() = cu | + result = t.getType().(RefType).getSourceDeclaration() + ) or + exists(ArrayTypeAccess at | at.getCompilationUnit() = cu | + result = at.getType().(Array).getElementType().(RefType).getSourceDeclaration() + ) or + // throws clauses + exists(Callable c | c.getCompilationUnit() = cu | + result = c.getAnException().getType() + ) or + // Javadoc + exists(JavadocElement j | cu.getFile() = j.getFile() | + result.getName() = neededByJavadoc(j) + ) +} + +RefType importedType(Import i) { + result = i.(ImportOnDemandFromPackage).getAnImport() or + result = i.(ImportOnDemandFromType).getAnImport() or + result = i.(ImportType).getImportedType() +} + +predicate neededImport(Import i) { + importedType(i) = neededType(i.getCompilationUnit()) +} + +from Import i +where + not neededImport(i) and + not i instanceof ImportStaticOnDemand and + not i instanceof ImportStaticTypeMember +select i, "The statement '" + i + "' is unnecessary." diff --git a/java/ql/src/config/semmlecode.dbscheme b/java/ql/src/config/semmlecode.dbscheme new file mode 100755 index 00000000000..87750e19e59 --- /dev/null +++ b/java/ql/src/config/semmlecode.dbscheme @@ -0,0 +1,821 @@ +/* + * External artifacts + */ + +externalData( + int id : @externalDataElement, + string path : string ref, + int column: int ref, + string value : string ref +); + +snapshotDate( + unique date snapshotDate : date ref +); + +sourceLocationPrefix( + string prefix : string ref +); + +/* + * Duplicate code + */ + +duplicateCode( + unique int id : @duplication, + string relativePath : string ref, + int equivClass : int ref +); + +similarCode( + unique int id : @similarity, + string relativePath : string ref, + int equivClass : int ref +); + +@duplication_or_similarity = @duplication | @similarity + +tokens( + int id : @duplication_or_similarity ref, + int offset : int ref, + int beginLine : int ref, + int beginColumn : int ref, + int endLine : int ref, + int endColumn : int ref +); + +/* + * Version history + */ + +svnentries( + int id : @svnentry, + string revision : string ref, + string author : string ref, + date revisionDate : date ref, + int changeSize : int ref +) + +svnaffectedfiles( + int id : @svnentry ref, + int file : @file ref, + string action : string ref +) + +svnentrymsg( + int id : @svnentry ref, + string message : string ref +) + +svnchurn( + int commit : @svnentry ref, + int file : @file ref, + int addedLines : int ref, + int deletedLines : int ref +) + +/* + * Locations and files + */ + +@location = @location_default ; + +locations_default( + unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref +); + +hasLocation( + int locatableid: @locatable ref, + int id: @location ref +); + +@sourceline = @locatable ; + +#keyset[element_id] +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref +); + +files( + unique int id: @file, + string name: string ref, + string simple: string ref, + string ext: string ref, + int fromSource: int ref // deprecated +); + +folders( + unique int id: @folder, + string name: string ref, + string simple: string ref +); + +@container = @folder | @file + +containerparent( + int parent: @container ref, + unique int child: @container ref +); + +/* + * Java + */ + +cupackage( + unique int id: @file ref, + int packageid: @package ref +); + +#keyset[fileid,keyName] +jarManifestMain( + int fileid: @file ref, + string keyName: string ref, + string value: string ref +); + +#keyset[fileid,entryName,keyName] +jarManifestEntries( + int fileid: @file ref, + string entryName: string ref, + string keyName: string ref, + string value: string ref +); + +packages( + unique int id: @package, + string nodeName: string ref +); + +primitives( + unique int id: @primitive, + string nodeName: string ref +); + +modifiers( + unique int id: @modifier, + string nodeName: string ref +); + +classes( + unique int id: @class, + string nodeName: string ref, + int parentid: @package ref, + int sourceid: @class ref +); + +interfaces( + unique int id: @interface, + string nodeName: string ref, + int parentid: @package ref, + int sourceid: @interface ref +); + +fielddecls( + unique int id: @fielddecl, + int parentid: @reftype ref +); + +#keyset[fieldId] #keyset[fieldDeclId,pos] +fieldDeclaredIn( + int fieldId: @field ref, + int fieldDeclId: @fielddecl ref, + int pos: int ref +); + +fields( + unique int id: @field, + string nodeName: string ref, + int typeid: @type ref, + int parentid: @reftype ref, + int sourceid: @field ref +); + +constrs( + unique int id: @constructor, + string nodeName: string ref, + string signature: string ref, + int typeid: @type ref, + int parentid: @reftype ref, + int sourceid: @constructor ref +); + +methods( + unique int id: @method, + string nodeName: string ref, + string signature: string ref, + int typeid: @type ref, + int parentid: @reftype ref, + int sourceid: @method ref +); + +#keyset[parentid,pos] +params( + unique int id: @param, + int typeid: @type ref, + int pos: int ref, + int parentid: @callable ref, + int sourceid: @param ref +); + +paramName( + unique int id: @param ref, + string nodeName: string ref +); + +isVarargsParam( + int param: @param ref +); + +exceptions( + unique int id: @exception, + int typeid: @type ref, + int parentid: @callable ref +); + +isAnnotType( + int interfaceid: @interface ref +); + +isAnnotElem( + int methodid: @method ref +); + +annotValue( + int parentid: @annotation ref, + int id2: @method ref, + unique int value: @expr ref +); + +isEnumType( + int classid: @class ref +); + +isEnumConst( + int fieldid: @field ref +); + +#keyset[parentid,pos] +typeVars( + unique int id: @typevariable, + string nodeName: string ref, + int pos: int ref, + int kind: int ref, // deprecated + int parentid: @typeorcallable ref +); + +wildcards( + unique int id: @wildcard, + string nodeName: string ref, + int kind: int ref +); + +#keyset[parentid,pos] +typeBounds( + unique int id: @typebound, + int typeid: @reftype ref, + int pos: int ref, + int parentid: @boundedtype ref +); + +#keyset[parentid,pos] +typeArgs( + int argumentid: @reftype ref, + int pos: int ref, + int parentid: @typeorcallable ref +); + +isParameterized( + int memberid: @member ref +); + +isRaw( + int memberid: @member ref +); + +erasure( + unique int memberid: @member ref, + int erasureid: @member ref +); + +#keyset[classid] #keyset[parent] +isAnonymClass( + int classid: @class ref, + int parent: @classinstancexpr ref +); + +#keyset[classid] #keyset[parent] +isLocalClass( + int classid: @class ref, + int parent: @localclassdeclstmt ref +); + +isDefConstr( + int constructorid: @constructor ref +); + +#keyset[exprId] +lambdaKind( + int exprId: @lambdaexpr ref, + int bodyKind: int ref +); + +arrays( + unique int id: @array, + string nodeName: string ref, + int elementtypeid: @type ref, + int dimension: int ref, + int componenttypeid: @type ref +); + +enclInReftype( + unique int child: @reftype ref, + int parent: @reftype ref +); + +extendsReftype( + int id1: @reftype ref, + int id2: @classorinterface ref +); + +implInterface( + int id1: @classorarray ref, + int id2: @interface ref +); + +hasModifier( + int id1: @modifiable ref, + int id2: @modifier ref +); + +imports( + unique int id: @import, + int holder: @typeorpackage ref, + string name: string ref, + int kind: int ref +); + +#keyset[parent,idx] +stmts( + unique int id: @stmt, + int kind: int ref, + int parent: @stmtparent ref, + int idx: int ref, + int bodydecl: @callable ref +); + +@stmtparent = @callable | @stmt; + +case @stmt.kind of + 0 = @block +| 1 = @ifstmt +| 2 = @forstmt +| 3 = @enhancedforstmt +| 4 = @whilestmt +| 5 = @dostmt +| 6 = @trystmt +| 7 = @switchstmt +| 8 = @synchronizedstmt +| 9 = @returnstmt +| 10 = @throwstmt +| 11 = @breakstmt +| 12 = @continuestmt +| 13 = @emptystmt +| 14 = @exprstmt +| 15 = @labeledstmt +| 16 = @assertstmt +| 17 = @localvariabledeclstmt +| 18 = @localclassdeclstmt +| 19 = @constructorinvocationstmt +| 20 = @superconstructorinvocationstmt +| 21 = @case +| 22 = @catchclause +; + +#keyset[parent,idx] +exprs( + unique int id: @expr, + int kind: int ref, + int typeid: @type ref, + int parent: @element ref, + int idx: int ref +); + +callableEnclosingExpr( + unique int id: @expr ref, + int callable_id: @callable ref +); + +statementEnclosingExpr( + unique int id: @expr ref, + int statement_id: @stmt ref +); + +case @expr.kind of + 1 = @arrayaccess +| 2 = @arraycreationexpr +| 3 = @arrayinit +| 4 = @assignexpr +| 5 = @assignaddexpr +| 6 = @assignsubexpr +| 7 = @assignmulexpr +| 8 = @assigndivexpr +| 9 = @assignremexpr +| 10 = @assignandexpr +| 11 = @assignorexpr +| 12 = @assignxorexpr +| 13 = @assignlshiftexpr +| 14 = @assignrshiftexpr +| 15 = @assignurshiftexpr +| 16 = @booleanliteral +| 17 = @integerliteral +| 18 = @longliteral +| 19 = @floatingpointliteral +| 20 = @doubleliteral +| 21 = @characterliteral +| 22 = @stringliteral +| 23 = @nullliteral +| 24 = @mulexpr +| 25 = @divexpr +| 26 = @remexpr +| 27 = @addexpr +| 28 = @subexpr +| 29 = @lshiftexpr +| 30 = @rshiftexpr +| 31 = @urshiftexpr +| 32 = @andbitexpr +| 33 = @orbitexpr +| 34 = @xorbitexpr +| 35 = @andlogicalexpr +| 36 = @orlogicalexpr +| 37 = @ltexpr +| 38 = @gtexpr +| 39 = @leexpr +| 40 = @geexpr +| 41 = @eqexpr +| 42 = @neexpr +| 43 = @postincexpr +| 44 = @postdecexpr +| 45 = @preincexpr +| 46 = @predecexpr +| 47 = @minusexpr +| 48 = @plusexpr +| 49 = @bitnotexpr +| 50 = @lognotexpr +| 51 = @castexpr +| 52 = @newexpr +| 53 = @conditionalexpr +| 54 = @parexpr +| 55 = @instanceofexpr +| 56 = @localvariabledeclexpr +| 57 = @typeliteral +| 58 = @thisaccess +| 59 = @superaccess +| 60 = @varaccess +| 61 = @methodaccess +| 62 = @unannotatedtypeaccess +| 63 = @arraytypeaccess +| 64 = @packageaccess +| 65 = @wildcardtypeaccess +| 66 = @declannotation +| 67 = @uniontypeaccess +| 68 = @lambdaexpr +| 69 = @memberref +| 70 = @annotatedtypeaccess +| 71 = @typeannotation +| 72 = @intersectiontypeaccess +; + +@classinstancexpr = @newexpr | @lambdaexpr | @memberref + +@annotation = @declannotation | @typeannotation +@typeaccess = @unannotatedtypeaccess | @annotatedtypeaccess + +@assignment = @assignexpr + | @assignop; + +@unaryassignment = @postincexpr + | @postdecexpr + | @preincexpr + | @predecexpr; + +@assignop = @assignaddexpr + | @assignsubexpr + | @assignmulexpr + | @assigndivexpr + | @assignremexpr + | @assignandexpr + | @assignorexpr + | @assignxorexpr + | @assignlshiftexpr + | @assignrshiftexpr + | @assignurshiftexpr; + +@literal = @booleanliteral + | @integerliteral + | @longliteral + | @floatingpointliteral + | @doubleliteral + | @characterliteral + | @stringliteral + | @nullliteral; + +@binaryexpr = @mulexpr + | @divexpr + | @remexpr + | @addexpr + | @subexpr + | @lshiftexpr + | @rshiftexpr + | @urshiftexpr + | @andbitexpr + | @orbitexpr + | @xorbitexpr + | @andlogicalexpr + | @orlogicalexpr + | @ltexpr + | @gtexpr + | @leexpr + | @geexpr + | @eqexpr + | @neexpr; + +@unaryexpr = @postincexpr + | @postdecexpr + | @preincexpr + | @predecexpr + | @minusexpr + | @plusexpr + | @bitnotexpr + | @lognotexpr; + +@caller = @classinstancexpr + | @methodaccess + | @constructorinvocationstmt + | @superconstructorinvocationstmt; + +callableBinding( + unique int callerid: @caller ref, + int callee: @callable ref +); + +memberRefBinding( + unique int id: @expr ref, + int callable: @callable ref +); + +@exprparent = @stmt | @expr | @callable | @field | @fielddecl | @class | @interface | @param | @localvar | @typevariable; + +variableBinding( + unique int expr: @varaccess ref, + int variable: @variable ref +); + +@variable = @localscopevariable | @field; + +@localscopevariable = @localvar | @param; + +localvars( + unique int id: @localvar, + string nodeName: string ref, + int typeid: @type ref, + int parentid: @localvariabledeclexpr ref +); + +@namedexprorstmt = @breakstmt + | @continuestmt + | @labeledstmt + | @literal; + +namestrings( + string name: string ref, + string value: string ref, + unique int parent: @namedexprorstmt ref +); + +/* + * Modules + */ + +#keyset[name] +modules( + unique int id: @module, + string name: string ref +); + +isOpen( + int id: @module ref +); + +#keyset[fileId] +cumodule( + int fileId: @file ref, + int moduleId: @module ref +); + +@directive = @requires + | @exports + | @opens + | @uses + | @provides + +#keyset[directive] +directives( + int id: @module ref, + int directive: @directive ref +); + +requires( + unique int id: @requires, + int target: @module ref +); + +isTransitive( + int id: @requires ref +); + +isStatic( + int id: @requires ref +); + +exports( + unique int id: @exports, + int target: @package ref +); + +exportsTo( + int id: @exports ref, + int target: @module ref +); + +opens( + unique int id: @opens, + int target: @package ref +); + +opensTo( + int id: @opens ref, + int target: @module ref +); + +uses( + unique int id: @uses, + string serviceInterface: string ref +); + +provides( + unique int id: @provides, + string serviceInterface: string ref +); + +providesWith( + int id: @provides ref, + string serviceImpl: string ref +); + +/* + * Javadoc + */ + +javadoc( + unique int id: @javadoc +); + +isNormalComment( + int commentid : @javadoc ref +); + +isEolComment( + int commentid : @javadoc ref +); + +hasJavadoc( + int documentableid: @member ref, + int javadocid: @javadoc ref +); + +#keyset[parentid,idx] +javadocTag( + unique int id: @javadocTag, + string name: string ref, + int parentid: @javadocParent ref, + int idx: int ref +); + +#keyset[parentid,idx] +javadocText( + unique int id: @javadocText, + string text: string ref, + int parentid: @javadocParent ref, + int idx: int ref +); + +@javadocParent = @javadoc | @javadocTag; +@javadocElement = @javadocTag | @javadocText; + +@typeorpackage = @type | @package; + +@typeorcallable = @type | @callable; +@classorinterface = @interface | @class; +@boundedtype = @typevariable | @wildcard; +@reftype = @classorinterface | @array | @boundedtype; +@classorarray = @class | @array; +@type = @primitive | @reftype; +@callable = @method | @constructor; +@element = @file | @package | @primitive | @class | @interface | @method | @constructor | @modifier | @param | @exception | @field | + @annotation | @boundedtype | @array | @localvar | @expr | @stmt | @import | @fielddecl; + +@modifiable = @member_modifiable| @param | @localvar ; + +@member_modifiable = @class | @interface | @method | @constructor | @field ; + +@member = @method | @constructor | @field | @reftype ; + +@locatable = @file | @class | @interface | @fielddecl | @field | @constructor | @method | @param | @exception + | @boundedtype | @typebound | @array | @primitive + | @import | @stmt | @expr | @localvar | @javadoc | @javadocTag | @javadocText + | @xmllocatable; + +@top = @element | @locatable | @folder; + +/* + * XML Files + */ + +xmlEncoding( + unique int id: @file ref, + string encoding: string ref +); + +xmlDTDs( + unique int id: @xmldtd, + string root: string ref, + string publicId: string ref, + string systemId: string ref, + int fileid: @file ref +); + +xmlElements( + unique int id: @xmlelement, + string name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref +); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + string name: string ref, + string value: string ref, + int idx: int ref, + int fileid: @file ref +); + +xmlNs( + int id: @xmlnamespace, + string prefixName: string ref, + string URI: string ref, + int fileid: @file ref +); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref +); + +xmlComments( + unique int id: @xmlcomment, + string text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref +); + +xmlChars( + unique int id: @xmlcharacters, + string text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref +); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref +); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + + diff --git a/java/ql/src/config/semmlecode.dbscheme.stats b/java/ql/src/config/semmlecode.dbscheme.stats new file mode 100644 index 00000000000..3f7eacf917f --- /dev/null +++ b/java/ql/src/config/semmlecode.dbscheme.stats @@ -0,0 +1,23441 @@ + + +@externalDataElement +1742 + + +@duplication +38760 + + +@similarity +125508 + + +@svnentry +575525 + + +@location_default +15321821 + + +@file +176620 + + +@folder +19172 + + +@package +10167 + + +@primitive +2904 + + +@modifier +2904 + + +@class +232016 + + +@interface +249736 + + +@fielddecl +99310 + + +@field +299208 + + +@constructor +262025 + + +@method +2355907 + + +@param +2259753 + + +@exception +510688 + + +@typevariable +58098 + + +@wildcard +39393 + + +@typebound +45015 + + +@array +31082 + + +@import +157518 + + +@block +682661 + + +@ifstmt +214215 + + +@forstmt +24683 + + +@enhancedforstmt +14815 + + +@whilestmt +14815 + + +@dostmt +3077 + + +@trystmt +28308 + + +@switchstmt +12518 + + +@synchronizedstmt +5693 + + +@returnstmt +216708 + + +@throwstmt +81919 + + +@breakstmt +47506 + + +@continuestmt +2817 + + +@emptystmt +523 + + +@exprstmt +785297 + + +@labeledstmt +3251 + + +@assertstmt +9478 + + +@localvariabledeclstmt +223232 + + +@localclassdeclstmt +349 + + +@constructorinvocationstmt +28177 + + +@superconstructorinvocationstmt +54771 + + +@case +82664 + + +@catchclause +28672 + + +@arrayaccess +53561 + + +@arraycreationexpr +35027 + + +@arrayinit +215504 + + +@assignexpr +385011 + + +@assignaddexpr +14076 + + +@assignsubexpr +6390 + + +@assignmulexpr +448 + + +@assigndivexpr +95 + + +@assignremexpr +39 + + +@assignandexpr +441 + + +@assignorexpr +11942 + + +@assignxorexpr +586 + + +@assignlshiftexpr +134 + + +@assignrshiftexpr +113 + + +@assignurshiftexpr +135 + + +@booleanliteral +1334750 + + +@integerliteral +345917 + + +@longliteral +56227 + + +@floatingpointliteral +16835 + + +@doubleliteral +5029 + + +@characterliteral +23950 + + +@stringliteral +584002 + + +@nullliteral +294342 + + +@mulexpr +17168 + + +@divexpr +3929 + + +@remexpr +1458 + + +@addexpr +123025 + + +@subexpr +22368 + + +@lshiftexpr +13943 + + +@rshiftexpr +12781 + + +@urshiftexpr +7862 + + +@andbitexpr +24111 + + +@orbitexpr +7136 + + +@xorbitexpr +1270 + + +@andlogicalexpr +29030 + + +@orlogicalexpr +40849 + + +@ltexpr +29652 + + +@gtexpr +30501 + + +@leexpr +7180 + + +@geexpr +10167 + + +@eqexpr +136556 + + +@neexpr +52986 + + +@postincexpr +36484 + + +@postdecexpr +15952 + + +@preincexpr +8700 + + +@predecexpr +3128 + + +@minusexpr +21600 + + +@plusexpr +165 + + +@bitnotexpr +6729 + + +@lognotexpr +31373 + + +@castexpr +123977 + + +@newexpr +177501 + + +@conditionalexpr +28468 + + +@parexpr +76040 + + +@instanceofexpr +24438 + + +@localvariabledeclexpr +269506 + + +@typeliteral +47201 + + +@thisaccess +81047 + + +@superaccess +10167 + + +@varaccess +2012792 + + +@methodaccess +1202564 + + +@unannotatedtypeaccess +1477426 + + +@arraytypeaccess +70880 + + +@wildcardtypeaccess +39865 + + +@declannotation +277067 + + +@uniontypeaccess +158 + + +@lambdaexpr +267 + + +@memberref +116 + + +@intersectiontypeaccess +15 + + +@packageaccess +1 + + +@annotatedtypeaccess +3 + + +@typeannotation +5 + + +@localvar +269506 + + +@module +72 + + +@requires +204 + + +@exports +495 + + +@opens +15 + + +@uses +108 + + +@provides +68 + + +@javadoc +596965 + + +@javadocTag +1048974 + + +@javadocText +3965535 + + +@xmldtd +1 + + +@xmlelement +1270313 + + +@xmlattribute +1202020 + + +@xmlnamespace +4185 + + +@xmlcomment +26812 + + +@xmlcharacters +439958 + + + +externalData +3485 + + +id +1742 + + +path +290 + + +column +580 + + +value +3485 + + + + +id +path + + +12 + + +1 +2 +1742 + + + + + + +id +column + + +12 + + +2 +3 +1742 + + + + + + +id +value + + +12 + + +2 +3 +1742 + + + + + + +path +id + + +12 + + +6 +7 +290 + + + + + + +path +column + + +12 + + +2 +3 +290 + + + + + + +path +value + + +12 + + +12 +13 +290 + + + + + + +column +id + + +12 + + +6 +7 +580 + + + + + + +column +path + + +12 + + +1 +2 +580 + + + + + + +column +value + + +12 + + +6 +7 +580 + + + + + + +value +id + + +12 + + +1 +2 +3485 + + + + + + +value +path + + +12 + + +1 +2 +3485 + + + + + + +value +column + + +12 + + +1 +2 +3485 + + + + + + + + +snapshotDate +290 + + +snapshotDate +290 + + + + + +sourceLocationPrefix +290 + + +prefix +290 + + + + + +duplicateCode +38760 + + +id +38760 + + +relativePath +977 + + +equivClass +9189 + + + + +id +relativePath + + +12 + + +1 +2 +38760 + + + + + + +id +equivClass + + +12 + + +1 +2 +38760 + + + + + + +relativePath +id + + +12 + + +1 +2 +195 + + +3 +4 +195 + + +4 +5 +48 + + +5 +6 +48 + + +8 +9 +146 + + +9 +10 +97 + + +10 +11 +48 + + +15 +16 +48 + + +24 +25 +97 + + +653 +654 +48 + + + + + + +relativePath +equivClass + + +12 + + +1 +2 +195 + + +3 +4 +244 + + +4 +5 +48 + + +5 +6 +48 + + +7 +8 +48 + + +8 +9 +146 + + +9 +10 +97 + + +24 +25 +97 + + +136 +137 +48 + + + + + + +equivClass +id + + +12 + + +1 +2 +439 + + +2 +3 +3568 + + +3 +4 +830 + + +4 +5 +782 + + +5 +6 +586 + + +6 +7 +830 + + +7 +8 +733 + + +8 +9 +488 + + +9 +10 +684 + + +10 +11 +244 + + + + + + +equivClass +relativePath + + +12 + + +1 +2 +6696 + + +2 +3 +1808 + + +3 +7 +684 + + + + + + + + +similarCode +125508 + + +id +125508 + + +relativePath +3504 + + +equivClass +31482 + + + + +id +relativePath + + +12 + + +1 +2 +125508 + + + + + + +id +equivClass + + +12 + + +1 +2 +125508 + + + + + + +relativePath +id + + +12 + + +1 +2 +634 + + +2 +3 +896 + + +3 +4 +271 + + +4 +5 +241 + + +5 +6 +211 + + +6 +7 +241 + + +7 +11 +312 + + +11 +16 +281 + + +16 +42 +271 + + +55 +3207 +140 + + + + + + +relativePath +equivClass + + +12 + + +1 +2 +976 + + +2 +3 +715 + + +3 +4 +332 + + +4 +5 +332 + + +5 +6 +231 + + +6 +7 +302 + + +7 +11 +312 + + +11 +777 +271 + + +783 +963 +30 + + + + + + +equivClass +id + + +12 + + +2 +3 +11853 + + +3 +4 +5569 + + +4 +5 +4109 + + +5 +6 +2477 + + +6 +7 +2336 + + +7 +8 +1712 + + +8 +10 +2537 + + +10 +11 +886 + + + + + + +equivClass +relativePath + + +12 + + +1 +2 +17957 + + +2 +3 +6506 + + +3 +4 +3112 + + +4 +5 +2125 + + +5 +11 +1782 + + + + + + + + +tokens +17070956 + + +id +139325 + + +offset +25832 + + +beginLine +243624 + + +beginColumn +10171 + + +endLine +243624 + + +endColumn +10141 + + + + +id +offset + + +12 + + +100 +101 +13284 + + +101 +102 +16426 + + +102 +103 +7442 + + +103 +105 +12176 + + +105 +108 +11763 + + +108 +112 +12276 + + +112 +116 +12810 + + +116 +120 +9668 + + +120 +128 +11531 + + +128 +143 +11088 + + +143 +169 +10846 + + +169 +2566 +10010 + + + + + + +id +beginLine + + +12 + + +4 +7 +9094 + + +7 +11 +11259 + + +11 +13 +7956 + + +13 +15 +12387 + + +15 +16 +6586 + + +16 +17 +9054 + + +17 +18 +8036 + + +18 +19 +9245 + + +19 +20 +9195 + + +20 +22 +12246 + + +22 +24 +12367 + + +24 +27 +12145 + + +27 +34 +11330 + + +34 +478 +8419 + + + + + + +id +beginColumn + + +12 + + +4 +32 +10786 + + +32 +41 +10705 + + +41 +44 +9426 + + +44 +46 +8751 + + +46 +48 +8701 + + +48 +50 +11138 + + +50 +52 +11964 + + +52 +54 +12337 + + +54 +56 +10323 + + +56 +59 +12166 + + +59 +63 +11662 + + +63 +70 +10897 + + +70 +144 +10464 + + + + + + +id +endLine + + +12 + + +4 +7 +9094 + + +7 +11 +11259 + + +11 +13 +7956 + + +13 +15 +12387 + + +15 +16 +6586 + + +16 +17 +9054 + + +17 +18 +8036 + + +18 +19 +9245 + + +19 +20 +9195 + + +20 +22 +12246 + + +22 +24 +12367 + + +24 +27 +12145 + + +27 +34 +11330 + + +34 +478 +8419 + + + + + + +id +endColumn + + +12 + + +4 +35 +10725 + + +35 +44 +12861 + + +44 +48 +11350 + + +48 +50 +9738 + + +50 +52 +10413 + + +52 +54 +11521 + + +54 +56 +12135 + + +56 +58 +9960 + + +58 +60 +9446 + + +60 +63 +10856 + + +63 +67 +11017 + + +67 +76 +10645 + + +76 +150 +8651 + + + + + + +offset +id + + +12 + + +4 +5 +14784 + + +10 +11 +2487 + + +14 +17 +1923 + + +18 +30 +2064 + + +31 +151 +1943 + + +152 +12516 +1621 + + +13834 +13835 +1007 + + + + + + +offset +beginLine + + +12 + + +1 +2 +14784 + + +7 +8 +2487 + + +8 +13 +2044 + + +13 +22 +1943 + + +22 +110 +1943 + + +112 +7742 +1943 + + +7742 +7961 +684 + + + + + + +offset +beginColumn + + +12 + + +1 +2 +14845 + + +2 +3 +2517 + + +3 +5 +1973 + + +5 +9 +2044 + + +9 +43 +1963 + + +43 +238 +1953 + + +238 +338 +533 + + + + + + +offset +endLine + + +12 + + +1 +2 +14784 + + +7 +8 +2487 + + +8 +13 +2044 + + +13 +22 +1943 + + +22 +110 +1943 + + +112 +7742 +1943 + + +7742 +7961 +684 + + + + + + +offset +endColumn + + +12 + + +1 +2 +14845 + + +2 +3 +2507 + + +3 +5 +2034 + + +5 +10 +2104 + + +10 +46 +1953 + + +46 +244 +1943 + + +244 +341 +443 + + + + + + +beginLine +id + + +12 + + +1 +2 +12830 + + +2 +3 +15499 + + +3 +4 +17463 + + +4 +5 +18762 + + +5 +6 +16788 + + +6 +7 +18309 + + +7 +8 +17604 + + +8 +9 +16728 + + +9 +10 +15680 + + +10 +11 +14824 + + +11 +12 +12720 + + +12 +14 +20988 + + +14 +18 +22438 + + +18 +44 +18380 + + +44 +418 +4602 + + + + + + +beginLine +offset + + +12 + + +1 +6 +18521 + + +6 +11 +20686 + + +11 +17 +21703 + + +17 +22 +18369 + + +22 +27 +18631 + + +27 +33 +21139 + + +33 +39 +19669 + + +39 +46 +19538 + + +46 +55 +20182 + + +55 +66 +19518 + + +66 +82 +18329 + + +82 +114 +18631 + + +114 +348 +8701 + + + + + + +beginLine +beginColumn + + +12 + + +1 +3 +20847 + + +3 +5 +15449 + + +5 +7 +21482 + + +7 +9 +15197 + + +9 +11 +19971 + + +11 +13 +18621 + + +13 +15 +17695 + + +15 +18 +21723 + + +18 +20 +16758 + + +20 +23 +22126 + + +23 +28 +20505 + + +28 +39 +19316 + + +39 +274 +13928 + + + + + + +beginLine +endLine + + +12 + + +1 +2 +243624 + + + + + + +beginLine +endColumn + + +12 + + +1 +3 +20495 + + +3 +5 +15207 + + +5 +7 +21069 + + +7 +9 +14965 + + +9 +11 +19558 + + +11 +13 +18813 + + +13 +15 +17262 + + +15 +18 +22076 + + +18 +20 +16980 + + +20 +23 +21995 + + +23 +28 +20938 + + +28 +38 +18772 + + +38 +274 +15489 + + + + + + +beginColumn +id + + +12 + + +1 +2 +674 + + +2 +3 +463 + + +3 +5 +896 + + +5 +8 +906 + + +8 +9 +161 + + +9 +10 +1057 + + +10 +20 +825 + + +20 +30 +815 + + +30 +44 +785 + + +44 +65 +765 + + +65 +102 +765 + + +102 +1024 +765 + + +1040 +5993 +765 + + +6023 +12021 +523 + + + + + + +beginColumn +offset + + +12 + + +1 +2 +956 + + +2 +3 +997 + + +3 +4 +1530 + + +4 +8 +845 + + +8 +13 +886 + + +13 +18 +856 + + +18 +24 +805 + + +24 +35 +825 + + +35 +62 +765 + + +66 +177 +765 + + +179 +380 +765 + + +382 +565 +171 + + + + + + +beginColumn +beginLine + + +12 + + +1 +2 +1057 + + +2 +3 +553 + + +3 +4 +715 + + +4 +5 +654 + + +5 +6 +1430 + + +6 +10 +835 + + +10 +15 +795 + + +15 +21 +785 + + +21 +30 +775 + + +30 +49 +765 + + +49 +658 +765 + + +681 +5371 +765 + + +5501 +12832 +271 + + + + + + +beginColumn +endLine + + +12 + + +1 +2 +1057 + + +2 +3 +553 + + +3 +4 +715 + + +4 +5 +654 + + +5 +6 +1430 + + +6 +10 +835 + + +10 +15 +795 + + +15 +21 +785 + + +21 +30 +775 + + +30 +49 +765 + + +49 +658 +765 + + +681 +5371 +765 + + +5501 +12832 +271 + + + + + + +beginColumn +endColumn + + +12 + + +1 +2 +3001 + + +2 +3 +1712 + + +3 +4 +1309 + + +4 +5 +1077 + + +5 +6 +674 + + +6 +9 +795 + + +9 +21 +785 + + +21 +51 +765 + + +51 +74 +50 + + + + + + +endLine +id + + +12 + + +1 +2 +12830 + + +2 +3 +15499 + + +3 +4 +17463 + + +4 +5 +18762 + + +5 +6 +16788 + + +6 +7 +18309 + + +7 +8 +17604 + + +8 +9 +16728 + + +9 +10 +15680 + + +10 +11 +14824 + + +11 +12 +12720 + + +12 +14 +20988 + + +14 +18 +22438 + + +18 +44 +18380 + + +44 +418 +4602 + + + + + + +endLine +offset + + +12 + + +1 +6 +18521 + + +6 +11 +20686 + + +11 +17 +21703 + + +17 +22 +18369 + + +22 +27 +18631 + + +27 +33 +21139 + + +33 +39 +19669 + + +39 +46 +19538 + + +46 +55 +20182 + + +55 +66 +19518 + + +66 +82 +18329 + + +82 +114 +18631 + + +114 +348 +8701 + + + + + + +endLine +beginLine + + +12 + + +1 +2 +243624 + + + + + + +endLine +beginColumn + + +12 + + +1 +3 +20847 + + +3 +5 +15449 + + +5 +7 +21482 + + +7 +9 +15197 + + +9 +11 +19971 + + +11 +13 +18621 + + +13 +15 +17695 + + +15 +18 +21723 + + +18 +20 +16758 + + +20 +23 +22126 + + +23 +28 +20505 + + +28 +39 +19316 + + +39 +274 +13928 + + + + + + +endLine +endColumn + + +12 + + +1 +3 +20495 + + +3 +5 +15207 + + +5 +7 +21069 + + +7 +9 +14965 + + +9 +11 +19558 + + +11 +13 +18813 + + +13 +15 +17262 + + +15 +18 +22076 + + +18 +20 +16980 + + +20 +23 +21995 + + +23 +28 +20938 + + +28 +38 +18772 + + +38 +274 +15489 + + + + + + +endColumn +id + + +12 + + +1 +2 +654 + + +2 +4 +896 + + +4 +6 +755 + + +6 +9 +604 + + +9 +10 +1047 + + +10 +19 +775 + + +19 +27 +785 + + +27 +38 +785 + + +38 +56 +765 + + +56 +90 +765 + + +91 +428 +765 + + +449 +3654 +765 + + +3777 +11083 +765 + + +12440 +12441 +10 + + + + + + +endColumn +offset + + +12 + + +1 +2 +825 + + +2 +3 +1117 + + +3 +4 +1470 + + +4 +9 +815 + + +9 +13 +845 + + +13 +17 +886 + + +17 +23 +805 + + +23 +34 +805 + + +34 +55 +765 + + +55 +166 +775 + + +166 +366 +765 + + +369 +520 +261 + + + + + + +endColumn +beginLine + + +12 + + +1 +2 +997 + + +2 +3 +382 + + +3 +4 +694 + + +4 +5 +654 + + +5 +6 +1379 + + +6 +9 +805 + + +9 +13 +785 + + +13 +18 +765 + + +18 +25 +775 + + +25 +38 +785 + + +38 +230 +765 + + +230 +2563 +765 + + +2595 +10410 +584 + + + + + + +endColumn +beginColumn + + +12 + + +1 +2 +2890 + + +2 +3 +1691 + + +3 +4 +1490 + + +4 +5 +1017 + + +5 +7 +926 + + +7 +12 +795 + + +12 +31 +785 + + +31 +49 +543 + + + + + + +endColumn +endLine + + +12 + + +1 +2 +997 + + +2 +3 +382 + + +3 +4 +694 + + +4 +5 +654 + + +5 +6 +1379 + + +6 +9 +805 + + +9 +13 +785 + + +13 +18 +765 + + +18 +25 +775 + + +25 +38 +785 + + +38 +230 +765 + + +230 +2563 +765 + + +2595 +10410 +584 + + + + + + + + +svnentries +575525 + + +id +575525 + + +revision +575525 + + +author +19539 + + +revisionDate +547759 + + +changeSize +1 + + + + +id +revision + + +12 + + +1 +2 +575525 + + + + + + +id +author + + +12 + + +1 +2 +575525 + + + + + + +id +revisionDate + + +12 + + +1 +2 +575525 + + + + + + +id +changeSize + + +12 + + +1 +2 +575525 + + + + + + +revision +id + + +12 + + +1 +2 +575525 + + + + + + +revision +author + + +12 + + +1 +2 +575525 + + + + + + +revision +revisionDate + + +12 + + +1 +2 +575525 + + + + + + +revision +changeSize + + +12 + + +1 +2 +575525 + + + + + + +author +id + + +12 + + +1 +2 +7913 + + +2 +3 +2531 + + +3 +4 +1388 + + +4 +6 +1523 + + +6 +10 +1529 + + +10 +20 +1509 + + +20 +52 +1488 + + +52 +568 +1466 + + +569 +16582 +192 + + + + + + +author +revision + + +12 + + +1 +2 +7913 + + +2 +3 +2531 + + +3 +4 +1388 + + +4 +6 +1523 + + +6 +10 +1529 + + +10 +20 +1509 + + +20 +52 +1488 + + +52 +568 +1466 + + +569 +16582 +192 + + + + + + +author +revisionDate + + +12 + + +1 +2 +7996 + + +2 +3 +2509 + + +3 +4 +1379 + + +4 +6 +1520 + + +6 +10 +1529 + + +10 +20 +1507 + + +20 +52 +1474 + + +52 +662 +1466 + + +663 +16573 +159 + + + + + + +author +changeSize + + +12 + + +1 +2 +19539 + + + + + + +revisionDate +id + + +12 + + +1 +2 +531878 + + +2 +100 +15881 + + + + + + +revisionDate +revision + + +12 + + +1 +2 +531878 + + +2 +100 +15881 + + + + + + +revisionDate +author + + +12 + + +1 +2 +542505 + + +2 +17 +5254 + + + + + + +revisionDate +changeSize + + +12 + + +1 +2 +547759 + + + + + + +changeSize +id + + +12 + + +575525 +575526 +1 + + + + + + +changeSize +revision + + +12 + + +575525 +575526 +1 + + + + + + +changeSize +author + + +12 + + +19539 +19540 +1 + + + + + + +changeSize +revisionDate + + +12 + + +547759 +547760 +1 + + + + + + + + +svnaffectedfiles +1314068 + + +id +531628 + + +file +90924 + + +action +1 + + + + +id +file + + +12 + + +1 +2 +337698 + + +2 +3 +77525 + + +3 +4 +43024 + + +4 +7 +46689 + + +7 +16635 +26692 + + + + + + +id +action + + +12 + + +1 +2 +531628 + + + + + + +file +id + + +12 + + +1 +2 +11819 + + +2 +3 +18230 + + +3 +4 +9501 + + +4 +5 +6656 + + +5 +6 +5012 + + +6 +8 +7103 + + +8 +11 +6788 + + +11 +16 +6996 + + +16 +26 +7180 + + +26 +54 +6824 + + +54 +3572 +4815 + + + + + + +file +action + + +12 + + +1 +2 +90924 + + + + + + +action +id + + +12 + + +531628 +531629 +1 + + + + + + +action +file + + +12 + + +90924 +90925 +1 + + + + + + + + +svnentrymsg +575525 + + +id +575525 + + +message +568305 + + + + +id +message + + +12 + + +1 +2 +575525 + + + + + + +message +id + + +12 + + +1 +2 +565381 + + +2 +142 +2924 + + + + + + + + +svnchurn +46790 + + +commit +22361 + + +file +16124 + + +addedLines +910 + + +deletedLines +787 + + + + +commit +file + + +12 + + +1 +2 +15208 + + +2 +3 +3101 + + +3 +4 +1746 + + +4 +8 +1774 + + +8 +246 +532 + + + + + + +commit +addedLines + + +12 + + +1 +2 +16074 + + +2 +3 +3323 + + +3 +4 +1561 + + +4 +118 +1403 + + + + + + +commit +deletedLines + + +12 + + +1 +2 +16799 + + +2 +3 +3286 + + +3 +5 +1763 + + +5 +113 +513 + + + + + + +file +commit + + +12 + + +1 +2 +8618 + + +2 +3 +2956 + + +3 +4 +1426 + + +4 +6 +1364 + + +6 +12 +1210 + + +12 +448 +550 + + + + + + +file +addedLines + + +12 + + +1 +2 +9240 + + +2 +3 +3129 + + +3 +4 +1393 + + +4 +6 +1239 + + +6 +59 +1123 + + + + + + +file +deletedLines + + +12 + + +1 +2 +9525 + + +2 +3 +3192 + + +3 +4 +1401 + + +4 +7 +1387 + + +7 +70 +619 + + + + + + +addedLines +commit + + +12 + + +1 +2 +446 + + +2 +3 +133 + + +3 +4 +70 + + +4 +6 +68 + + +6 +12 +70 + + +12 +57 +69 + + +57 +6874 +54 + + + + + + +addedLines +file + + +12 + + +1 +2 +445 + + +2 +3 +132 + + +3 +4 +69 + + +4 +6 +68 + + +6 +12 +73 + + +12 +58 +69 + + +58 +6663 +54 + + + + + + +addedLines +deletedLines + + +12 + + +1 +2 +621 + + +2 +3 +96 + + +3 +7 +81 + + +7 +34 +70 + + +34 +727 +42 + + + + + + +deletedLines +commit + + +12 + + +1 +2 +439 + + +2 +3 +116 + + +3 +4 +48 + + +4 +8 +67 + + +8 +28 +60 + + +28 +6794 +57 + + + + + + +deletedLines +file + + +12 + + +1 +2 +437 + + +2 +3 +113 + + +3 +4 +49 + + +4 +7 +61 + + +7 +19 +60 + + +19 +770 +60 + + +985 +7318 +7 + + + + + + +deletedLines +addedLines + + +12 + + +1 +2 +545 + + +2 +3 +72 + + +3 +7 +69 + + +7 +30 +60 + + +30 +871 +41 + + + + + + + + +locations_default +15321821 + + +id +15321821 + + +file +176620 + + +beginLine +865672 + + +beginColumn +42121 + + +endLine +883102 + + +endColumn +43574 + + + + +id +file + + +12 + + +1 +2 +15321821 + + + + + + +id +beginLine + + +12 + + +1 +2 +15321821 + + + + + + +id +beginColumn + + +12 + + +1 +2 +15321821 + + + + + + +id +endLine + + +12 + + +1 +2 +15321821 + + + + + + +id +endColumn + + +12 + + +1 +2 +15321821 + + + + + + +file +id + + +12 + + +1 +2 +146118 + + +51 +203 +13653 + + +211 +790 +13362 + + +926 +7096 +3485 + + + + + + +file +beginLine + + +12 + + +1 +2 +146118 + + +35 +89 +13362 + + +89 +277 +13362 + + +292 +2423 +3776 + + + + + + +file +beginColumn + + +12 + + +1 +2 +146118 + + +14 +42 +13943 + + +42 +84 +13362 + + +87 +136 +3195 + + + + + + +file +endLine + + +12 + + +1 +2 +146118 + + +36 +94 +13362 + + +95 +306 +13362 + + +329 +2637 +3776 + + + + + + +file +endColumn + + +12 + + +1 +2 +146118 + + +32 +67 +13653 + + +68 +98 +13362 + + +99 +138 +3485 + + + + + + +beginLine +id + + +12 + + +1 +2 +73204 + + +2 +3 +79014 + + +3 +4 +88310 + + +4 +5 +87729 + + +5 +6 +63037 + + +6 +7 +59841 + + +7 +8 +52288 + + +8 +10 +70009 + + +10 +13 +71171 + + +13 +20 +70590 + + +20 +49 +65651 + + +49 +137 +65361 + + +137 +609 +19463 + + + + + + +beginLine +file + + +12 + + +1 +2 +201893 + + +2 +3 +355564 + + +3 +4 +103125 + + +4 +8 +74947 + + +8 +24 +66813 + + +24 +609 +63327 + + + + + + +beginLine +beginColumn + + +12 + + +1 +2 +108935 + + +2 +3 +88891 + + +3 +4 +139437 + + +4 +5 +82209 + + +5 +6 +70880 + + +6 +7 +50836 + + +7 +9 +78433 + + +9 +12 +74075 + + +12 +19 +67685 + + +19 +35 +68847 + + +35 +57 +35440 + + + + + + +beginLine +endLine + + +12 + + +1 +2 +487158 + + +2 +3 +227747 + + +3 +4 +60132 + + +4 +8 +72623 + + +8 +15 +18010 + + + + + + +beginLine +endColumn + + +12 + + +1 +2 +83662 + + +2 +3 +83081 + + +3 +4 +94701 + + +4 +5 +108063 + + +5 +6 +74947 + + +6 +7 +55484 + + +7 +8 +52869 + + +8 +10 +61875 + + +10 +13 +65651 + + +13 +21 +65651 + + +21 +43 +65070 + + +43 +76 +54612 + + + + + + +beginColumn +id + + +12 + + +1 +3 +3776 + + +3 +5 +2614 + + +5 +10 +3195 + + +10 +19 +3485 + + +19 +28 +3195 + + +30 +54 +3195 + + +54 +79 +3195 + + +86 +131 +3195 + + +149 +198 +3195 + + +198 +383 +3195 + + +392 +550 +3195 + + +596 +917 +3195 + + +918 +4396 +3195 + + +7284 +7285 +290 + + + + + + +beginColumn +file + + +12 + + +1 +2 +2323 + + +2 +3 +2904 + + +3 +5 +2614 + + +5 +7 +2904 + + +7 +10 +3485 + + +10 +16 +3776 + + +16 +24 +3195 + + +25 +38 +3485 + + +39 +49 +3195 + + +51 +61 +3485 + + +64 +72 +3195 + + +72 +85 +3195 + + +85 +105 +2614 + + +105 +609 +1742 + + + + + + +beginColumn +beginLine + + +12 + + +1 +2 +1742 + + +2 +3 +2323 + + +3 +5 +2614 + + +5 +10 +3195 + + +10 +19 +3776 + + +20 +28 +3195 + + +29 +49 +3195 + + +49 +71 +3195 + + +73 +111 +3195 + + +126 +158 +3195 + + +159 +247 +3195 + + +256 +348 +3195 + + +353 +577 +3195 + + +580 +2365 +2904 + + + + + + +beginColumn +endLine + + +12 + + +1 +2 +1742 + + +2 +3 +2323 + + +3 +5 +2614 + + +5 +10 +3195 + + +10 +19 +3776 + + +20 +28 +3195 + + +29 +50 +3195 + + +51 +71 +3195 + + +74 +111 +3195 + + +126 +157 +3195 + + +160 +250 +3195 + + +262 +342 +3195 + + +357 +579 +3195 + + +579 +2365 +2904 + + + + + + +beginColumn +endColumn + + +12 + + +1 +2 +3485 + + +2 +3 +4357 + + +3 +6 +3195 + + +6 +9 +3776 + + +9 +14 +3195 + + +14 +21 +3195 + + +21 +29 +3485 + + +29 +34 +3195 + + +34 +42 +3195 + + +43 +55 +3485 + + +55 +67 +3195 + + +67 +105 +3776 + + +107 +112 +580 + + + + + + +endLine +id + + +12 + + +1 +2 +56355 + + +2 +3 +95863 + + +3 +4 +91215 + + +4 +5 +101091 + + +5 +6 +64780 + + +6 +7 +62746 + + +7 +8 +52869 + + +8 +10 +74075 + + +10 +13 +70299 + + +13 +21 +69718 + + +21 +55 +67104 + + +55 +157 +66523 + + +157 +609 +10457 + + + + + + +endLine +file + + +12 + + +1 +2 +131884 + + +2 +3 +406982 + + +3 +4 +125202 + + +4 +7 +70299 + + +7 +18 +66523 + + +18 +75 +66813 + + +76 +609 +15396 + + + + + + +endLine +beginLine + + +12 + + +1 +2 +514755 + + +2 +3 +225713 + + +3 +5 +79304 + + +5 +12 +63327 + + + + + + +endLine +beginColumn + + +12 + + +1 +2 +89472 + + +2 +3 +107773 + + +3 +4 +140889 + + +4 +5 +102834 + + +5 +6 +71461 + + +6 +7 +56646 + + +7 +9 +80176 + + +9 +12 +69137 + + +12 +20 +66232 + + +20 +37 +68847 + + +37 +58 +29630 + + + + + + +endLine +endColumn + + +12 + + +1 +2 +68556 + + +2 +3 +101091 + + +3 +4 +97606 + + +4 +5 +124912 + + +5 +6 +69428 + + +6 +7 +63618 + + +7 +8 +50255 + + +8 +10 +67104 + + +10 +14 +74947 + + +14 +26 +66232 + + +26 +52 +66523 + + +52 +78 +32825 + + + + + + +endColumn +id + + +12 + + +1 +4 +3776 + + +4 +7 +3485 + + +7 +30 +3485 + + +31 +58 +3485 + + +58 +104 +3485 + + +105 +176 +3485 + + +192 +398 +3485 + + +402 +472 +3485 + + +474 +579 +3485 + + +580 +657 +3485 + + +677 +783 +3485 + + +797 +1060 +3485 + + +1109 +2048 +1452 + + + + + + +endColumn +file + + +12 + + +1 +2 +3195 + + +2 +5 +3485 + + +5 +10 +3485 + + +10 +16 +3485 + + +16 +25 +3485 + + +25 +40 +3485 + + +42 +67 +3485 + + +67 +78 +3485 + + +78 +82 +3485 + + +82 +87 +3485 + + +87 +100 +3485 + + +101 +105 +1452 + + +105 +106 +3776 + + +608 +609 +290 + + + + + + +endColumn +beginLine + + +12 + + +1 +3 +3776 + + +3 +7 +3776 + + +7 +18 +3485 + + +18 +40 +3485 + + +40 +72 +3485 + + +72 +126 +3485 + + +135 +266 +3485 + + +268 +312 +3485 + + +313 +359 +3485 + + +364 +406 +3485 + + +419 +477 +3485 + + +485 +586 +3485 + + +597 +1097 +1161 + + + + + + +endColumn +beginColumn + + +12 + + +1 +2 +3776 + + +2 +4 +2614 + + +4 +6 +3485 + + +6 +10 +3485 + + +10 +19 +3485 + + +19 +26 +3485 + + +26 +32 +3485 + + +32 +36 +3195 + + +36 +40 +2904 + + +40 +47 +3776 + + +47 +52 +3776 + + +52 +56 +3776 + + +56 +121 +2323 + + + + + + +endColumn +endLine + + +12 + + +1 +3 +3776 + + +3 +7 +3776 + + +7 +18 +3485 + + +21 +41 +3485 + + +43 +71 +3485 + + +72 +124 +3485 + + +135 +266 +3485 + + +267 +311 +3485 + + +312 +358 +3485 + + +359 +406 +3485 + + +419 +477 +3485 + + +486 +594 +3485 + + +605 +1097 +1161 + + + + + + + + +hasLocation +18893736 + + +locatableid +18342088 + + +id +15321821 + + + + +locatableid +id + + +12 + + +1 +2 +17790439 + + +2 +3 +551648 + + + + + + +id +locatableid + + +12 + + +1 +2 +15155948 + + +2 +581 +165872 + + + + + + + + +numlines +8182637 + + +element_id +8182637 + + +num_lines +55193 + + +num_code +33406 + + +num_comment +38345 + + + + +element_id +num_lines + + +12 + + +1 +2 +8182637 + + + + + + +element_id +num_code + + +12 + + +1 +2 +8182637 + + + + + + +element_id +num_comment + + +12 + + +1 +2 +8182637 + + + + + + +num_lines +element_id + + +12 + + +1 +2 +30211 + + +2 +3 +10748 + + +3 +6 +4938 + + +6 +17 +4357 + + +19 +327 +4357 + + +1772 +24169 +580 + + + + + + +num_lines +num_code + + +12 + + +1 +2 +32535 + + +2 +3 +10457 + + +3 +4 +4647 + + +4 +5 +3485 + + +5 +9 +4066 + + + + + + +num_lines +num_comment + + +12 + + +1 +2 +32825 + + +2 +3 +9876 + + +3 +4 +5519 + + +4 +5 +4357 + + +5 +7 +2614 + + + + + + +num_code +element_id + + +12 + + +1 +2 +13362 + + +2 +3 +5519 + + +3 +4 +1742 + + +4 +5 +2614 + + +5 +10 +2614 + + +10 +21 +2614 + + +26 +111 +2614 + + +136 +24169 +2323 + + + + + + +num_code +num_lines + + +12 + + +1 +2 +13943 + + +2 +3 +6390 + + +3 +4 +3485 + + +4 +5 +1742 + + +5 +6 +1742 + + +6 +8 +2614 + + +8 +12 +2614 + + +12 +15 +871 + + + + + + +num_code +num_comment + + +12 + + +1 +2 +13943 + + +2 +3 +6390 + + +3 +4 +3195 + + +4 +5 +2614 + + +5 +7 +2904 + + +7 +10 +2904 + + +10 +15 +1452 + + + + + + +num_comment +element_id + + +12 + + +1 +2 +20334 + + +2 +3 +7552 + + +3 +4 +3195 + + +4 +6 +3195 + + +6 +16 +2904 + + +33 +27486 +1161 + + + + + + +num_comment +num_lines + + +12 + + +1 +2 +20915 + + +2 +3 +8133 + + +3 +4 +2904 + + +4 +5 +3195 + + +5 +31 +2904 + + +31 +32 +290 + + + + + + +num_comment +num_code + + +12 + + +1 +2 +21206 + + +2 +3 +7552 + + +3 +4 +3485 + + +4 +5 +2904 + + +5 +31 +3195 + + + + + + + + +files +176620 + + +id +176620 + + +name +176620 + + +simple +145537 + + +ext +1161 + + +fromSource +580 + + + + +id +name + + +12 + + +1 +2 +176620 + + + + + + +id +simple + + +12 + + +1 +2 +176620 + + + + + + +id +ext + + +12 + + +1 +2 +176620 + + + + + + +id +fromSource + + +12 + + +1 +2 +176620 + + + + + + +name +id + + +12 + + +1 +2 +176620 + + + + + + +name +simple + + +12 + + +1 +2 +176620 + + + + + + +name +ext + + +12 + + +1 +2 +176620 + + + + + + +name +fromSource + + +12 + + +1 +2 +176620 + + + + + + +simple +id + + +12 + + +1 +2 +114745 + + +2 +3 +30501 + + +3 +4 +290 + + + + + + +simple +name + + +12 + + +1 +2 +114745 + + +2 +3 +30501 + + +3 +4 +290 + + + + + + +simple +ext + + +12 + + +1 +2 +115035 + + +2 +3 +30501 + + + + + + +simple +fromSource + + +12 + + +1 +2 +145247 + + +2 +3 +290 + + + + + + +ext +id + + +12 + + +1 +2 +580 + + +105 +106 +290 + + +501 +502 +290 + + + + + + +ext +name + + +12 + + +1 +2 +580 + + +105 +106 +290 + + +501 +502 +290 + + + + + + +ext +simple + + +12 + + +1 +2 +580 + + +105 +106 +290 + + +499 +500 +290 + + + + + + +ext +fromSource + + +12 + + +1 +2 +871 + + +2 +3 +290 + + + + + + +fromSource +id + + +12 + + +218 +219 +290 + + +390 +391 +290 + + + + + + +fromSource +name + + +12 + + +218 +219 +290 + + +390 +391 +290 + + + + + + +fromSource +simple + + +12 + + +113 +114 +290 + + +389 +390 +290 + + + + + + +fromSource +ext + + +12 + + +1 +2 +290 + + +4 +5 +290 + + + + + + + + +folders +19172 + + +id +19172 + + +name +19172 + + +simple +13362 + + + + +id +name + + +12 + + +1 +2 +19172 + + + + + + +id +simple + + +12 + + +1 +2 +19172 + + + + + + +name +id + + +12 + + +1 +2 +19172 + + + + + + +name +simple + + +12 + + +1 +2 +19172 + + + + + + +simple +id + + +12 + + +1 +2 +8133 + + +2 +3 +4647 + + +3 +4 +580 + + + + + + +simple +name + + +12 + + +1 +2 +8133 + + +2 +3 +4647 + + +3 +4 +580 + + + + + + + + +containerparent +195212 + + +parent +19463 + + +child +195212 + + + + +parent +child + + +12 + + +1 +2 +7552 + + +2 +4 +1742 + + +4 +5 +871 + + +5 +7 +1742 + + +7 +10 +1452 + + +10 +14 +1742 + + +18 +25 +1452 + + +25 +30 +1742 + + +34 +73 +1161 + + + + + + +child +parent + + +12 + + +1 +2 +195212 + + + + + + + + +cupackage +176039 + + +id +176039 + + +packageid +10167 + + + + +id +packageid + + +12 + + +1 +2 +176039 + + + + + + +packageid +id + + +12 + + +1 +2 +1452 + + +3 +4 +871 + + +4 +5 +871 + + +5 +7 +871 + + +7 +8 +580 + + +9 +10 +580 + + +10 +11 +1161 + + +12 +21 +871 + + +21 +35 +871 + + +42 +48 +871 + + +50 +59 +871 + + +69 +70 +290 + + + + + + + + +jarManifestMain +6737 + + +fileid +453 + + +keyName +533 + + +value +3081 + + + + +fileid +keyName + + +12 + + +3 +5 +40 + + +5 +6 +30 + + +6 +11 +40 + + +12 +13 +110 + + +13 +14 +40 + + +15 +18 +30 + + +19 +21 +40 + + +21 +25 +40 + + +25 +26 +10 + + +26 +27 +50 + + +27 +29 +20 + + + + + + +fileid +value + + +12 + + +3 +5 +40 + + +5 +6 +30 + + +6 +9 +30 + + +9 +10 +100 + + +10 +11 +50 + + +11 +16 +40 + + +16 +17 +30 + + +18 +19 +50 + + +19 +21 +40 + + +21 +23 +40 + + + + + + +keyName +fileid + + +12 + + +1 +2 +140 + + +2 +3 +50 + + +3 +5 +30 + + +5 +7 +40 + + +7 +14 +40 + + +15 +16 +20 + + +16 +18 +40 + + +19 +20 +50 + + +24 +29 +40 + + +30 +31 +20 + + +31 +38 +40 + + +45 +46 +20 + + + + + + +keyName +value + + +12 + + +1 +2 +211 + + +2 +4 +40 + + +4 +5 +40 + + +5 +7 +40 + + +7 +9 +40 + + +10 +16 +40 + + +16 +18 +40 + + +19 +20 +40 + + +24 +30 +40 + + + + + + +value +fileid + + +12 + + +1 +2 +2447 + + +2 +3 +312 + + +3 +6 +231 + + +6 +46 +90 + + + + + + +value +keyName + + +12 + + +1 +2 +2457 + + +2 +3 +412 + + +3 +6 +211 + + + + + + + + +jarManifestEntries +10748 + + +fileid +290 + + +entryName +10748 + + +keyName +290 + + +value +290 + + + + +fileid +entryName + + +12 + + +37 +38 +290 + + + + + + +fileid +keyName + + +12 + + +1 +2 +290 + + + + + + +fileid +value + + +12 + + +1 +2 +290 + + + + + + +entryName +fileid + + +12 + + +1 +2 +10748 + + + + + + +entryName +keyName + + +12 + + +1 +2 +10748 + + + + + + +entryName +value + + +12 + + +1 +2 +10748 + + + + + + +keyName +fileid + + +12 + + +1 +2 +290 + + + + + + +keyName +entryName + + +12 + + +37 +38 +290 + + + + + + +keyName +value + + +12 + + +1 +2 +290 + + + + + + +value +fileid + + +12 + + +1 +2 +290 + + + + + + +value +entryName + + +12 + + +37 +38 +290 + + + + + + +value +keyName + + +12 + + +1 +2 +290 + + + + + + + + +packages +10167 + + +id +10167 + + +nodeName +10167 + + + + +id +nodeName + + +12 + + +1 +2 +10167 + + + + + + +nodeName +id + + +12 + + +1 +2 +10167 + + + + + + + + +primitives +2904 + + +id +2904 + + +nodeName +2904 + + + + +id +nodeName + + +12 + + +1 +2 +2904 + + + + + + +nodeName +id + + +12 + + +1 +2 +2904 + + + + + + + + +modifiers +2904 + + +id +2904 + + +nodeName +2904 + + + + +id +nodeName + + +12 + + +1 +2 +2904 + + + + + + +nodeName +id + + +12 + + +1 +2 +2904 + + + + + + + + +classes +232016 + + +id +232016 + + +nodeName +124693 + + +parentid +1143 + + +sourceid +54791 + + + + +id +nodeName + + +12 + + +1 +2 +232016 + + + + + + +id +parentid + + +12 + + +1 +2 +232016 + + + + + + +id +sourceid + + +12 + + +1 +2 +232016 + + + + + + +nodeName +id + + +12 + + +1 +2 +110648 + + +2 +5 +10125 + + +5 +2207 +3919 + + + + + + +nodeName +parentid + + +12 + + +1 +2 +123244 + + +2 +20 +1449 + + + + + + +nodeName +sourceid + + +12 + + +1 +2 +121778 + + +2 +2207 +2915 + + + + + + +parentid +id + + +12 + + +1 +2 +218 + + +2 +3 +87 + + +3 +4 +87 + + +4 +5 +69 + + +5 +7 +96 + + +7 +10 +96 + + +10 +12 +104 + + +12 +23 +87 + + +25 +44 +96 + + +47 +189 +87 + + +211 +2179 +87 + + +3173 +9683 +26 + + + + + + +parentid +nodeName + + +12 + + +1 +2 +218 + + +2 +3 +87 + + +3 +4 +87 + + +4 +5 +69 + + +5 +7 +104 + + +7 +10 +87 + + +10 +12 +104 + + +12 +18 +87 + + +18 +29 +87 + + +39 +114 +87 + + +124 +585 +87 + + +1269 +4602 +34 + + + + + + +parentid +sourceid + + +12 + + +1 +2 +218 + + +2 +3 +87 + + +3 +4 +96 + + +4 +5 +78 + + +5 +7 +104 + + +7 +9 +104 + + +9 +12 +104 + + +12 +23 +96 + + +23 +44 +87 + + +47 +123 +87 + + +169 +1844 +78 + + + + + + +sourceid +id + + +12 + + +1 +2 +46516 + + +2 +5 +4879 + + +5 +1831 +3395 + + + + + + +sourceid +nodeName + + +12 + + +1 +2 +46516 + + +2 +4 +4827 + + +4 +1410 +3447 + + + + + + +sourceid +parentid + + +12 + + +1 +2 +54791 + + + + + + + + +interfaces +249736 + + +id +249736 + + +nodeName +69229 + + +parentid +925 + + +sourceid +5438 + + + + +id +nodeName + + +12 + + +1 +2 +249736 + + + + + + +id +parentid + + +12 + + +1 +2 +249736 + + + + + + +id +sourceid + + +12 + + +1 +2 +249736 + + + + + + +nodeName +id + + +12 + + +1 +2 +57960 + + +2 +4 +5787 + + +4 +126 +5193 + + +130 +640 +288 + + + + + + +nodeName +parentid + + +12 + + +1 +2 +68863 + + +2 +4 +366 + + + + + + +nodeName +sourceid + + +12 + + +1 +2 +68836 + + +2 +10 +392 + + + + + + +parentid +id + + +12 + + +1 +2 +253 + + +2 +3 +104 + + +3 +4 +96 + + +4 +5 +52 + + +5 +6 +69 + + +6 +9 +78 + + +10 +19 +69 + + +19 +38 +69 + + +42 +235 +69 + + +244 +15903 +61 + + + + + + +parentid +nodeName + + +12 + + +1 +2 +253 + + +2 +3 +104 + + +3 +4 +96 + + +4 +5 +61 + + +5 +7 +78 + + +7 +11 +69 + + +12 +17 +78 + + +17 +38 +69 + + +38 +183 +69 + + +227 +4122 +43 + + + + + + +parentid +sourceid + + +12 + + +1 +2 +270 + + +2 +3 +96 + + +3 +4 +104 + + +4 +5 +61 + + +5 +6 +87 + + +6 +8 +78 + + +8 +11 +69 + + +12 +15 +61 + + +15 +23 +69 + + +24 +38 +26 + + + + + + +sourceid +id + + +12 + + +1 +2 +4242 + + +2 +9 +410 + + +9 +52 +410 + + +66 +5544 +375 + + + + + + +sourceid +nodeName + + +12 + + +1 +2 +4242 + + +2 +6 +445 + + +6 +20 +418 + + +21 +1202 +331 + + + + + + +sourceid +parentid + + +12 + + +1 +2 +5438 + + + + + + + + +fielddecls +99310 + + +id +99310 + + +parentid +15500 + + + + +id +parentid + + +12 + + +1 +2 +99310 + + + + + + +parentid +id + + +12 + + +1 +2 +4363 + + +2 +3 +2837 + + +3 +4 +1952 + + +4 +5 +1413 + + +5 +6 +962 + + +6 +8 +1179 + + +8 +13 +1335 + + +13 +41 +1173 + + +41 +1438 +286 + + + + + + + + +fieldDeclaredIn +101480 + + +fieldId +101480 + + +fieldDeclId +99310 + + +pos +205 + + + + +fieldId +fieldDeclId + + +12 + + +1 +2 +101480 + + + + + + +fieldId +pos + + +12 + + +1 +2 +101480 + + + + + + +fieldDeclId +fieldId + + +12 + + +1 +2 +98741 + + +2 +206 +569 + + + + + + +fieldDeclId +pos + + +12 + + +1 +2 +98741 + + +2 +206 +569 + + + + + + +pos +fieldId + + +12 + + +1 +2 +2 + + +2 +3 +78 + + +3 +4 +43 + + +4 +7 +15 + + +7 +8 +18 + + +8 +9 +18 + + +9 +20 +16 + + +23 +99311 +15 + + + + + + +pos +fieldDeclId + + +12 + + +1 +2 +2 + + +2 +3 +78 + + +3 +4 +43 + + +4 +7 +15 + + +7 +8 +18 + + +8 +9 +18 + + +9 +20 +16 + + +23 +99311 +15 + + + + + + + + +fields +299208 + + +id +299208 + + +nodeName +210027 + + +typeid +47060 + + +parentid +93539 + + +sourceid +299208 + + + + +id +nodeName + + +12 + + +1 +2 +299208 + + + + + + +id +typeid + + +12 + + +1 +2 +299208 + + + + + + +id +parentid + + +12 + + +1 +2 +299208 + + + + + + +id +sourceid + + +12 + + +1 +2 +299208 + + + + + + +nodeName +id + + +12 + + +1 +2 +185044 + + +2 +4 +18591 + + +4 +110 +6390 + + + + + + +nodeName +typeid + + +12 + + +1 +2 +196374 + + +2 +8 +13653 + + + + + + +nodeName +parentid + + +12 + + +1 +2 +185044 + + +2 +4 +18591 + + +4 +110 +6390 + + + + + + +nodeName +sourceid + + +12 + + +1 +2 +185044 + + +2 +4 +18591 + + +4 +110 +6390 + + + + + + +typeid +id + + +12 + + +1 +2 +28177 + + +2 +3 +5228 + + +3 +4 +4357 + + +4 +8 +3776 + + +8 +23 +3776 + + +30 +326 +1742 + + + + + + +typeid +nodeName + + +12 + + +1 +2 +29630 + + +2 +3 +5519 + + +3 +4 +3776 + + +4 +9 +4066 + + +9 +73 +3776 + + +179 +180 +290 + + + + + + +typeid +parentid + + +12 + + +1 +2 +35440 + + +2 +3 +3776 + + +3 +6 +4066 + + +6 +177 +3776 + + + + + + +typeid +sourceid + + +12 + + +1 +2 +28177 + + +2 +3 +5228 + + +3 +4 +4357 + + +4 +8 +3776 + + +8 +23 +3776 + + +30 +326 +1742 + + + + + + +parentid +id + + +12 + + +1 +2 +51417 + + +2 +3 +12781 + + +3 +4 +9876 + + +4 +7 +8424 + + +7 +12 +7552 + + +12 +67 +3485 + + + + + + +parentid +nodeName + + +12 + + +1 +2 +51417 + + +2 +3 +12781 + + +3 +4 +9876 + + +4 +7 +8424 + + +7 +12 +7552 + + +12 +67 +3485 + + + + + + +parentid +typeid + + +12 + + +1 +2 +61875 + + +2 +3 +13943 + + +3 +4 +7843 + + +4 +6 +7552 + + +6 +9 +2323 + + + + + + +parentid +sourceid + + +12 + + +1 +2 +51417 + + +2 +3 +12781 + + +3 +4 +9876 + + +4 +7 +8424 + + +7 +12 +7552 + + +12 +67 +3485 + + + + + + +sourceid +id + + +12 + + +1 +2 +299208 + + + + + + +sourceid +nodeName + + +12 + + +1 +2 +299208 + + + + + + +sourceid +typeid + + +12 + + +1 +2 +299208 + + + + + + +sourceid +parentid + + +12 + + +1 +2 +299208 + + + + + + + + +constrs +262025 + + +id +262025 + + +nodeName +120845 + + +signature +257087 + + +typeid +290 + + +parentid +126945 + + +sourceid +225713 + + + + +id +nodeName + + +12 + + +1 +2 +262025 + + + + + + +id +signature + + +12 + + +1 +2 +262025 + + + + + + +id +typeid + + +12 + + +1 +2 +262025 + + + + + + +id +parentid + + +12 + + +1 +2 +262025 + + + + + + +id +sourceid + + +12 + + +1 +2 +262025 + + + + + + +nodeName +id + + +12 + + +1 +2 +60132 + + +2 +3 +30501 + + +3 +4 +8714 + + +4 +5 +11038 + + +5 +10 +9586 + + +10 +18 +871 + + + + + + +nodeName +signature + + +12 + + +1 +2 +61003 + + +2 +3 +31082 + + +3 +4 +8424 + + +4 +5 +10457 + + +5 +11 +9295 + + +12 +18 +580 + + + + + + +nodeName +typeid + + +12 + + +1 +2 +120845 + + + + + + +nodeName +parentid + + +12 + + +1 +2 +118231 + + +2 +9 +2614 + + + + + + +nodeName +sourceid + + +12 + + +1 +2 +61003 + + +2 +3 +30792 + + +3 +4 +8424 + + +4 +5 +10748 + + +5 +11 +9295 + + +12 +18 +580 + + + + + + +signature +id + + +12 + + +1 +2 +255053 + + +2 +9 +2033 + + + + + + +signature +nodeName + + +12 + + +1 +2 +257087 + + + + + + +signature +typeid + + +12 + + +1 +2 +257087 + + + + + + +signature +parentid + + +12 + + +1 +2 +255053 + + +2 +9 +2033 + + + + + + +signature +sourceid + + +12 + + +1 +2 +256506 + + +2 +3 +580 + + + + + + +typeid +id + + +12 + + +902 +903 +290 + + + + + + +typeid +nodeName + + +12 + + +416 +417 +290 + + + + + + +typeid +signature + + +12 + + +885 +886 +290 + + + + + + +typeid +parentid + + +12 + + +437 +438 +290 + + + + + + +typeid +sourceid + + +12 + + +777 +778 +290 + + + + + + +parentid +id + + +12 + + +1 +2 +67394 + + +2 +3 +31082 + + +3 +4 +8424 + + +4 +5 +10457 + + +5 +18 +9586 + + + + + + +parentid +nodeName + + +12 + + +1 +2 +126945 + + + + + + +parentid +signature + + +12 + + +1 +2 +67394 + + +2 +3 +31082 + + +3 +4 +8424 + + +4 +5 +10457 + + +5 +18 +9586 + + + + + + +parentid +typeid + + +12 + + +1 +2 +126945 + + + + + + +parentid +sourceid + + +12 + + +1 +2 +67394 + + +2 +3 +31082 + + +3 +4 +8424 + + +4 +5 +10457 + + +5 +18 +9586 + + + + + + +sourceid +id + + +12 + + +1 +2 +212641 + + +2 +18 +13072 + + + + + + +sourceid +nodeName + + +12 + + +1 +2 +212641 + + +2 +11 +13072 + + + + + + +sourceid +signature + + +12 + + +1 +2 +212641 + + +2 +11 +13072 + + + + + + +sourceid +typeid + + +12 + + +1 +2 +225713 + + + + + + +sourceid +parentid + + +12 + + +1 +2 +212641 + + +2 +18 +13072 + + + + + + + + +methods +2355907 + + +id +2355907 + + +nodeName +556296 + + +signature +870610 + + +typeid +189402 + + +parentid +220485 + + +sourceid +1516379 + + + + +id +nodeName + + +12 + + +1 +2 +2355907 + + + + + + +id +signature + + +12 + + +1 +2 +2355907 + + + + + + +id +typeid + + +12 + + +1 +2 +2355907 + + + + + + +id +parentid + + +12 + + +1 +2 +2355907 + + + + + + +id +sourceid + + +12 + + +1 +2 +2355907 + + + + + + +nodeName +id + + +12 + + +1 +2 +325643 + + +2 +3 +97606 + + +3 +4 +31663 + + +4 +7 +46188 + + +7 +22 +41831 + + +22 +194 +13362 + + + + + + +nodeName +signature + + +12 + + +1 +2 +442713 + + +2 +3 +71171 + + +3 +34 +41831 + + +41 +51 +580 + + + + + + +nodeName +typeid + + +12 + + +1 +2 +480186 + + +2 +3 +36021 + + +3 +109 +40088 + + + + + + +nodeName +parentid + + +12 + + +1 +2 +368637 + + +2 +3 +72914 + + +3 +5 +47641 + + +5 +9 +43864 + + +9 +177 +23239 + + + + + + +nodeName +sourceid + + +12 + + +1 +2 +350335 + + +2 +3 +105449 + + +3 +5 +50545 + + +5 +20 +42121 + + +20 +151 +7843 + + + + + + +signature +id + + +12 + + +1 +2 +640539 + + +2 +3 +102253 + + +3 +6 +73204 + + +6 +177 +54612 + + + + + + +signature +nodeName + + +12 + + +1 +2 +870610 + + + + + + +signature +typeid + + +12 + + +1 +2 +803797 + + +2 +19 +65651 + + +21 +109 +1161 + + + + + + +signature +parentid + + +12 + + +1 +2 +640539 + + +2 +3 +102253 + + +3 +6 +73204 + + +6 +177 +54612 + + + + + + +signature +sourceid + + +12 + + +1 +2 +665521 + + +2 +3 +113292 + + +3 +7 +69137 + + +7 +151 +22658 + + + + + + +typeid +id + + +12 + + +1 +2 +72042 + + +2 +3 +35440 + + +3 +4 +21496 + + +4 +5 +12491 + + +5 +7 +15396 + + +7 +12 +14524 + + +12 +39 +14234 + + +43 +1655 +3776 + + + + + + +typeid +nodeName + + +12 + + +1 +2 +109225 + + +2 +3 +33987 + + +3 +4 +17720 + + +4 +8 +14815 + + +8 +452 +13653 + + + + + + +typeid +signature + + +12 + + +1 +2 +98477 + + +2 +3 +38926 + + +3 +4 +18010 + + +4 +7 +14234 + + +7 +21 +14234 + + +23 +719 +5519 + + + + + + +typeid +parentid + + +12 + + +1 +2 +93248 + + +2 +3 +43574 + + +3 +4 +22658 + + +4 +7 +14234 + + +7 +88 +14234 + + +94 +435 +1452 + + + + + + +typeid +sourceid + + +12 + + +1 +2 +75237 + + +2 +3 +36602 + + +3 +4 +22658 + + +4 +6 +17139 + + +6 +9 +17429 + + +9 +22 +14234 + + +25 +1341 +6100 + + + + + + +parentid +id + + +12 + + +1 +2 +66523 + + +2 +3 +12491 + + +3 +4 +14815 + + +4 +6 +16558 + + +6 +9 +18882 + + +9 +12 +18591 + + +12 +15 +15977 + + +15 +17 +19753 + + +17 +28 +17429 + + +28 +68 +18010 + + +70 +125 +1452 + + + + + + +parentid +nodeName + + +12 + + +1 +2 +68847 + + +2 +3 +15396 + + +3 +4 +14524 + + +4 +6 +16848 + + +6 +8 +15105 + + +8 +10 +12781 + + +10 +14 +19172 + + +14 +15 +19753 + + +15 +22 +16558 + + +22 +43 +16558 + + +45 +74 +4938 + + + + + + +parentid +signature + + +12 + + +1 +2 +66523 + + +2 +3 +12491 + + +3 +4 +14815 + + +4 +6 +16558 + + +6 +9 +18882 + + +9 +12 +18591 + + +12 +15 +15977 + + +15 +17 +19753 + + +17 +28 +17429 + + +28 +68 +18010 + + +70 +125 +1452 + + + + + + +parentid +typeid + + +12 + + +1 +2 +76980 + + +2 +3 +21787 + + +3 +4 +20334 + + +4 +5 +15105 + + +5 +6 +13943 + + +6 +7 +19172 + + +7 +8 +21787 + + +8 +10 +13653 + + +10 +33 +17429 + + +46 +47 +290 + + + + + + +parentid +sourceid + + +12 + + +1 +2 +66523 + + +2 +3 +12491 + + +3 +4 +14815 + + +4 +6 +16558 + + +6 +9 +18882 + + +9 +12 +18591 + + +12 +15 +15977 + + +15 +17 +19753 + + +17 +28 +17429 + + +28 +68 +18010 + + +70 +125 +1452 + + + + + + +sourceid +id + + +12 + + +1 +2 +1349054 + + +2 +8 +139437 + + +10 +35 +27887 + + + + + + +sourceid +nodeName + + +12 + + +1 +2 +1516379 + + + + + + +sourceid +signature + + +12 + + +1 +2 +1488201 + + +2 +34 +28177 + + + + + + +sourceid +typeid + + +12 + + +1 +2 +1447822 + + +2 +32 +68556 + + + + + + +sourceid +parentid + + +12 + + +1 +2 +1349054 + + +2 +8 +139437 + + +10 +35 +27887 + + + + + + + + +params +2259753 + + +id +2259753 + + +typeid +144956 + + +pos +3195 + + +parentid +1514636 + + +sourceid +1680508 + + + + +id +typeid + + +12 + + +1 +2 +2259753 + + + + + + +id +pos + + +12 + + +1 +2 +2259753 + + + + + + +id +parentid + + +12 + + +1 +2 +2259753 + + + + + + +id +sourceid + + +12 + + +1 +2 +2259753 + + + + + + +typeid +id + + +12 + + +1 +2 +63618 + + +2 +3 +20915 + + +3 +4 +13072 + + +4 +5 +8133 + + +5 +8 +12491 + + +8 +15 +11619 + + +15 +72 +11038 + + +72 +1788 +4066 + + + + + + +typeid +pos + + +12 + + +1 +2 +105158 + + +2 +3 +26434 + + +3 +5 +11329 + + +5 +11 +2033 + + + + + + +typeid +parentid + + +12 + + +1 +2 +64489 + + +2 +3 +20334 + + +3 +4 +13072 + + +4 +5 +8424 + + +5 +8 +12491 + + +8 +15 +11038 + + +15 +65 +11038 + + +71 +1305 +4066 + + + + + + +typeid +sourceid + + +12 + + +1 +2 +68847 + + +2 +3 +19172 + + +3 +4 +13072 + + +4 +6 +13362 + + +6 +11 +12491 + + +11 +37 +11038 + + +40 +1331 +6971 + + + + + + +pos +id + + +12 + + +3 +4 +580 + + +5 +6 +290 + + +10 +11 +290 + + +18 +19 +290 + + +32 +33 +290 + + +61 +62 +290 + + +176 +177 +290 + + +562 +563 +290 + + +1695 +1696 +290 + + +5214 +5215 +290 + + + + + + +pos +typeid + + +12 + + +1 +2 +580 + + +3 +4 +871 + + +4 +5 +290 + + +22 +23 +290 + + +31 +32 +290 + + +67 +68 +290 + + +198 +199 +290 + + +386 +387 +290 + + + + + + +pos +parentid + + +12 + + +3 +4 +580 + + +5 +6 +290 + + +10 +11 +290 + + +18 +19 +290 + + +32 +33 +290 + + +61 +62 +290 + + +176 +177 +290 + + +562 +563 +290 + + +1695 +1696 +290 + + +5214 +5215 +290 + + + + + + +pos +sourceid + + +12 + + +3 +4 +580 + + +5 +6 +290 + + +10 +11 +290 + + +18 +19 +290 + + +32 +33 +290 + + +61 +62 +290 + + +173 +174 +290 + + +544 +545 +290 + + +1392 +1393 +290 + + +3544 +3545 +290 + + + + + + +parentid +id + + +12 + + +1 +2 +1022248 + + +2 +3 +329129 + + +3 +4 +112130 + + +4 +12 +51126 + + + + + + +parentid +typeid + + +12 + + +1 +2 +1107944 + + +2 +3 +332034 + + +3 +7 +74656 + + + + + + +parentid +pos + + +12 + + +1 +2 +1022248 + + +2 +3 +329129 + + +3 +4 +112130 + + +4 +12 +51126 + + + + + + +parentid +sourceid + + +12 + + +1 +2 +1022248 + + +2 +3 +329129 + + +3 +4 +112130 + + +4 +12 +51126 + + + + + + +sourceid +id + + +12 + + +1 +2 +1558791 + + +2 +35 +121717 + + + + + + +sourceid +typeid + + +12 + + +1 +2 +1631705 + + +2 +35 +48803 + + + + + + +sourceid +pos + + +12 + + +1 +2 +1680508 + + + + + + +sourceid +parentid + + +12 + + +1 +2 +1558791 + + +2 +35 +121717 + + + + + + + + +paramName +542282 + + +id +542282 + + +nodeName +7603 + + + + +id +nodeName + + +12 + + +1 +2 +542282 + + + + + + +nodeName +id + + +12 + + +1 +2 +3528 + + +2 +3 +1325 + + +3 +4 +594 + + +4 +6 +651 + + +6 +11 +653 + + +11 +39 +571 + + +39 +300389 +281 + + + + + + + + +isVarargsParam +34392 + + +param +34392 + + + + + +exceptions +510688 + + +id +510688 + + +typeid +17139 + + +parentid +443874 + + + + +id +typeid + + +12 + + +1 +2 +510688 + + + + + + +id +parentid + + +12 + + +1 +2 +510688 + + + + + + +typeid +id + + +12 + + +1 +2 +2614 + + +2 +3 +4066 + + +3 +4 +1452 + + +4 +5 +1742 + + +5 +9 +1452 + + +9 +11 +1161 + + +13 +20 +1452 + + +24 +29 +1161 + + +33 +100 +1452 + + +120 +1040 +580 + + + + + + +typeid +parentid + + +12 + + +1 +2 +2614 + + +2 +3 +4066 + + +3 +4 +1452 + + +4 +5 +1742 + + +5 +9 +1452 + + +9 +11 +1161 + + +13 +20 +1452 + + +24 +29 +1161 + + +33 +100 +1452 + + +120 +1040 +580 + + + + + + +parentid +id + + +12 + + +1 +2 +386938 + + +2 +3 +49093 + + +3 +6 +7843 + + + + + + +parentid +typeid + + +12 + + +1 +2 +386938 + + +2 +3 +49093 + + +3 +6 +7843 + + + + + + + + +isAnnotType +2323 + + +interfaceid +2323 + + + + + +isAnnotElem +871 + + +methodid +871 + + + + + +annotValue +1405734 + + +parentid +155253 + + +id2 +261 + + +value +1405734 + + + + +parentid +id2 + + +12 + + +1 +2 +37290 + + +2 +3 +117963 + + + + + + +parentid +value + + +12 + + +1 +2 +18391 + + +2 +3 +66148 + + +3 +4 +12412 + + +4 +5 +19675 + + +5 +6 +17 + + +6 +7 +15546 + + +7 +23 +12386 + + +24 +1297 +10675 + + + + + + +id2 +parentid + + +12 + + +1 +2 +26 + + +2 +3 +17 + + +3 +4 +26 + + +4 +5 +17 + + +8 +9 +34 + + +30 +49 +17 + + +51 +52 +17 + + +79 +85 +17 + + +252 +253 +17 + + +296 +297 +17 + + +464 +465 +17 + + +1184 +2842 +17 + + +12426 +12427 +17 + + + + + + +id2 +value + + +12 + + +1 +2 +26 + + +3 +4 +43 + + +5 +8 +17 + + +8 +9 +8 + + +12 +13 +17 + + +16 +39 +17 + + +60 +70 +17 + + +84 +94 +17 + + +219 +300 +17 + + +373 +474 +17 + + +531 +664 +17 + + +729 +3035 +17 + + +3805 +75171 +17 + + +75324 +75325 +8 + + + + + + +value +parentid + + +12 + + +1 +2 +1405734 + + + + + + +value +id2 + + +12 + + +1 +2 +1405734 + + + + + + + + +isEnumType +2749 + + +classid +2749 + + + + + +isEnumConst +24624 + + +fieldid +24624 + + + + + +typeVars +58098 + + +id +58098 + + +nodeName +2033 + + +pos +580 + + +kind +290 + + +parentid +49674 + + + + +id +nodeName + + +12 + + +1 +2 +58098 + + + + + + +id +pos + + +12 + + +1 +2 +58098 + + + + + + +id +kind + + +12 + + +1 +2 +58098 + + + + + + +id +parentid + + +12 + + +1 +2 +58098 + + + + + + +nodeName +id + + +12 + + +1 +2 +290 + + +3 +4 +290 + + +8 +9 +290 + + +27 +28 +290 + + +28 +29 +290 + + +31 +32 +290 + + +102 +103 +290 + + + + + + +nodeName +pos + + +12 + + +1 +2 +1161 + + +2 +3 +871 + + + + + + +nodeName +kind + + +12 + + +1 +2 +2033 + + + + + + +nodeName +parentid + + +12 + + +1 +2 +290 + + +3 +4 +290 + + +8 +9 +290 + + +27 +28 +290 + + +28 +29 +290 + + +31 +32 +290 + + +102 +103 +290 + + + + + + +pos +id + + +12 + + +29 +30 +290 + + +171 +172 +290 + + + + + + +pos +nodeName + + +12 + + +3 +4 +290 + + +7 +8 +290 + + + + + + +pos +kind + + +12 + + +1 +2 +580 + + + + + + +pos +parentid + + +12 + + +29 +30 +290 + + +171 +172 +290 + + + + + + +kind +id + + +12 + + +200 +201 +290 + + + + + + +kind +nodeName + + +12 + + +7 +8 +290 + + + + + + +kind +pos + + +12 + + +2 +3 +290 + + + + + + +kind +parentid + + +12 + + +171 +172 +290 + + + + + + +parentid +id + + +12 + + +1 +2 +41250 + + +2 +3 +8424 + + + + + + +parentid +nodeName + + +12 + + +1 +2 +41250 + + +2 +3 +8424 + + + + + + +parentid +pos + + +12 + + +1 +2 +41250 + + +2 +3 +8424 + + + + + + +parentid +kind + + +12 + + +1 +2 +49674 + + + + + + + + +wildcards +39393 + + +id +39393 + + +nodeName +10919 + + +kind +17 + + + + +id +nodeName + + +12 + + +1 +2 +39393 + + + + + + +id +kind + + +12 + + +1 +2 +39393 + + + + + + +nodeName +id + + +12 + + +1 +2 +9549 + + +2 +5 +846 + + +5 +541 +523 + + + + + + +nodeName +kind + + +12 + + +1 +2 +10919 + + + + + + +kind +id + + +12 + + +1361 +1362 +8 + + +3152 +3153 +8 + + + + + + +kind +nodeName + + +12 + + +476 +477 +8 + + +775 +776 +8 + + + + + + + + +typeBounds +45015 + + +id +45015 + + +typeid +33973 + + +pos +34 + + +parentid +44666 + + + + +id +typeid + + +12 + + +1 +2 +45015 + + + + + + +id +pos + + +12 + + +1 +2 +45015 + + + + + + +id +parentid + + +12 + + +1 +2 +45015 + + + + + + +typeid +id + + +12 + + +1 +2 +25733 + + +2 +3 +7751 + + +3 +67 +488 + + + + + + +typeid +pos + + +12 + + +1 +2 +33851 + + +2 +4 +122 + + + + + + +typeid +parentid + + +12 + + +1 +2 +25733 + + +2 +3 +7751 + + +3 +67 +488 + + + + + + +pos +id + + +12 + + +1 +2 +8 + + +11 +12 +8 + + +28 +29 +8 + + +5117 +5118 +8 + + + + + + +pos +typeid + + +12 + + +1 +2 +8 + + +10 +11 +8 + + +15 +16 +8 + + +3883 +3884 +8 + + + + + + +pos +parentid + + +12 + + +1 +2 +8 + + +11 +12 +8 + + +28 +29 +8 + + +5117 +5118 +8 + + + + + + +parentid +id + + +12 + + +1 +2 +44421 + + +2 +5 +244 + + + + + + +parentid +typeid + + +12 + + +1 +2 +44421 + + +2 +5 +244 + + + + + + +parentid +pos + + +12 + + +1 +2 +44421 + + +2 +5 +244 + + + + + + + + +typeArgs +533376 + + +argumentid +112350 + + +pos +34 + + +parentid +412488 + + + + +argumentid +pos + + +12 + + +1 +2 +90659 + + +2 +3 +20216 + + +3 +5 +1475 + + + + + + +argumentid +parentid + + +12 + + +1 +2 +33545 + + +2 +3 +20984 + + +3 +4 +17222 + + +4 +5 +9383 + + +5 +7 +8283 + + +7 +9 +5900 + + +9 +12 +9060 + + +12 +1286 +7969 + + + + + + +pos +argumentid + + +12 + + +83 +84 +8 + + +386 +387 +8 + + +3736 +3737 +8 + + +11327 +11328 +8 + + + + + + +pos +parentid + + +12 + + +115 +116 +8 + + +1167 +1168 +8 + + +12567 +12568 +8 + + +47255 +47256 +8 + + + + + + +parentid +argumentid + + +12 + + +1 +2 +309826 + + +2 +3 +93391 + + +3 +5 +9270 + + + + + + +parentid +pos + + +12 + + +1 +2 +302791 + + +2 +3 +99510 + + +3 +5 +10186 + + + + + + + + +isParameterized +412488 + + +memberid +412488 + + + + + +isRaw +13653 + + +memberid +13653 + + + + + +erasure +421523 + + +memberid +421523 + + +erasureid +9470 + + + + +memberid +erasureid + + +12 + + +1 +2 +421523 + + + + + + +erasureid +memberid + + +12 + + +1 +2 +3317 + + +2 +3 +977 + + +3 +4 +846 + + +4 +5 +663 + + +5 +7 +689 + + +7 +11 +733 + + +11 +20 +733 + + +20 +65 +715 + + +65 +1025 +715 + + +1073 +5543 +78 + + + + + + + + +isAnonymClass +33159 + + +classid +33159 + + +parent +33159 + + + + +classid +parent + + +12 + + +1 +2 +33159 + + + + + + +parent +classid + + +12 + + +1 +2 +33159 + + + + + + + + +isLocalClass +349 + + +classid +349 + + +parent +349 + + + + +classid +parent + + +12 + + +1 +2 +349 + + + + + + +parent +classid + + +12 + + +1 +2 +349 + + + + + + + + +isDefConstr +43941 + + +constructorid +43941 + + + + + +lambdaKind +267 + + +exprId +267 + + +bodyKind +2 + + + + +exprId +bodyKind + + +12 + + +1 +2 +267 + + + + + + +bodyKind +exprId + + +12 + + +113 +114 +1 + + +154 +155 +1 + + + + + + + + +arrays +31082 + + +id +31082 + + +nodeName +24401 + + +elementtypeid +30501 + + +dimension +580 + + +componenttypeid +31082 + + + + +id +nodeName + + +12 + + +1 +2 +31082 + + + + + + +id +elementtypeid + + +12 + + +1 +2 +31082 + + + + + + +id +dimension + + +12 + + +1 +2 +31082 + + + + + + +id +componenttypeid + + +12 + + +1 +2 +31082 + + + + + + +nodeName +id + + +12 + + +1 +2 +22949 + + +2 +21 +1452 + + + + + + +nodeName +elementtypeid + + +12 + + +1 +2 +22949 + + +2 +21 +1452 + + + + + + +nodeName +dimension + + +12 + + +1 +2 +24401 + + + + + + +nodeName +componenttypeid + + +12 + + +1 +2 +22949 + + +2 +21 +1452 + + + + + + +elementtypeid +id + + +12 + + +1 +2 +29920 + + +2 +3 +580 + + + + + + +elementtypeid +nodeName + + +12 + + +1 +2 +29920 + + +2 +3 +580 + + + + + + +elementtypeid +dimension + + +12 + + +1 +2 +29920 + + +2 +3 +580 + + + + + + +elementtypeid +componenttypeid + + +12 + + +1 +2 +29920 + + +2 +3 +580 + + + + + + +dimension +id + + +12 + + +2 +3 +290 + + +105 +106 +290 + + + + + + +dimension +nodeName + + +12 + + +2 +3 +290 + + +82 +83 +290 + + + + + + +dimension +elementtypeid + + +12 + + +2 +3 +290 + + +105 +106 +290 + + + + + + +dimension +componenttypeid + + +12 + + +2 +3 +290 + + +105 +106 +290 + + + + + + +componenttypeid +id + + +12 + + +1 +2 +31082 + + + + + + +componenttypeid +nodeName + + +12 + + +1 +2 +31082 + + + + + + +componenttypeid +elementtypeid + + +12 + + +1 +2 +31082 + + + + + + +componenttypeid +dimension + + +12 + + +1 +2 +31082 + + + + + + + + +enclInReftype +117415 + + +child +117415 + + +parent +7223 + + + + +child +parent + + +12 + + +1 +2 +117415 + + + + + + +parent +child + + +12 + + +1 +2 +3113 + + +2 +3 +2353 + + +3 +4 +525 + + +4 +7 +638 + + +7 +49 +543 + + +49 +38837 +47 + + + + + + + + +extendsReftype +499769 + + +id1 +495597 + + +id2 +199291 + + + + +id1 +id2 + + +12 + + +1 +2 +491791 + + +2 +23 +3805 + + + + + + +id2 +id1 + + +12 + + +1 +2 +178219 + + +2 +3 +16611 + + +3 +27640 +4460 + + + + + + + + +implInterface +254182 + + +id1 +124621 + + +id2 +45607 + + + + +id1 +id2 + + +12 + + +1 +2 +49093 + + +2 +3 +45898 + + +3 +4 +5228 + + +4 +5 +24401 + + + + + + +id2 +id1 + + +12 + + +1 +2 +31082 + + +2 +3 +9295 + + +3 +15 +3485 + + +17 +281 +1742 + + + + + + + + +hasModifier +5667539 + + +id1 +3800824 + + +id2 +2904 + + + + +id1 +id2 + + +12 + + +1 +2 +2090395 + + +2 +3 +1554143 + + +3 +4 +156285 + + + + + + +id2 +id1 + + +12 + + +10 +11 +290 + + +27 +28 +290 + + +145 +146 +290 + + +328 +329 +290 + + +334 +335 +290 + + +420 +421 +290 + + +1916 +1917 +290 + + +3235 +3236 +290 + + +3317 +3318 +290 + + +9778 +9779 +290 + + + + + + + + +imports +157518 + + +id +157518 + + +holder +11495 + + +name +1327 + + +kind +10 + + + + +id +holder + + +12 + + +1 +2 +157518 + + + + + + +id +name + + +12 + + +1 +2 +157518 + + + + + + +id +kind + + +12 + + +1 +2 +157518 + + + + + + +holder +id + + +12 + + +1 +2 +3483 + + +2 +3 +2377 + + +3 +4 +1058 + + +4 +5 +784 + + +5 +7 +823 + + +7 +11 +903 + + +11 +20 +912 + + +20 +83 +867 + + +83 +2609 +284 + + + + + + +holder +name + + +12 + + +1 +2 +11286 + + +2 +222 +208 + + + + + + +holder +kind + + +12 + + +1 +2 +11240 + + +2 +4 +254 + + + + + + +name +id + + +12 + + +1 +2 +782 + + +2 +3 +217 + + +3 +4 +82 + + +4 +7 +115 + + +7 +36 +104 + + +56 +68984 +26 + + + + + + +name +holder + + +12 + + +1 +2 +1297 + + +2 +5272 +30 + + + + + + +name +kind + + +12 + + +1 +2 +1325 + + +4 +5 +2 + + + + + + +kind +id + + +12 + + +11 +12 +2 + + +446 +447 +2 + + +525 +526 +2 + + +3506 +3507 +2 + + +68001 +68002 +2 + + + + + + +kind +holder + + +12 + + +11 +12 +2 + + +58 +59 +2 + + +62 +63 +2 + + +111 +112 +2 + + +5189 +5190 +2 + + + + + + +kind +name + + +12 + + +1 +2 +8 + + +610 +611 +2 + + + + + + + + +stmts +1943115 + + +id +1943115 + + +kind +5809 + + +parent +1314195 + + +idx +6100 + + +bodydecl +346559 + + + + +id +kind + + +12 + + +1 +2 +1943115 + + + + + + +id +parent + + +12 + + +1 +2 +1943115 + + + + + + +id +idx + + +12 + + +1 +2 +1943115 + + + + + + +id +bodydecl + + +12 + + +1 +2 +1943115 + + + + + + +kind +id + + +12 + + +2 +3 +580 + + +3 +4 +290 + + +4 +5 +580 + + +8 +9 +290 + + +29 +30 +290 + + +41 +42 +290 + + +51 +52 +580 + + +72 +73 +290 + + +87 +88 +290 + + +97 +98 +290 + + +141 +142 +290 + + +282 +283 +290 + + +530 +531 +290 + + +728 +729 +290 + + +746 +747 +290 + + +1461 +1462 +290 + + +2350 +2351 +290 + + + + + + +kind +parent + + +12 + + +2 +3 +1161 + + +4 +5 +580 + + +28 +29 +290 + + +38 +39 +290 + + +49 +50 +290 + + +50 +51 +290 + + +67 +68 +290 + + +86 +87 +290 + + +97 +98 +290 + + +141 +142 +290 + + +281 +282 +290 + + +354 +355 +290 + + +515 +516 +290 + + +744 +745 +290 + + +903 +904 +290 + + +2232 +2233 +290 + + + + + + +kind +idx + + +12 + + +1 +2 +871 + + +2 +3 +580 + + +3 +4 +580 + + +4 +5 +290 + + +5 +6 +290 + + +6 +7 +871 + + +8 +9 +871 + + +10 +11 +290 + + +12 +13 +290 + + +14 +15 +290 + + +15 +16 +290 + + +19 +20 +290 + + + + + + +kind +bodydecl + + +12 + + +2 +3 +1161 + + +4 +5 +580 + + +23 +24 +290 + + +35 +36 +290 + + +48 +49 +290 + + +49 +50 +290 + + +63 +64 +290 + + +83 +84 +290 + + +97 +98 +290 + + +141 +142 +290 + + +180 +181 +290 + + +272 +273 +290 + + +343 +344 +290 + + +548 +549 +290 + + +587 +588 +290 + + +1193 +1194 +290 + + + + + + +parent +id + + +12 + + +1 +2 +1029801 + + +2 +3 +152799 + + +3 +6 +101382 + + +6 +20 +30211 + + + + + + +parent +kind + + +12 + + +1 +2 +1104458 + + +2 +3 +130431 + + +3 +7 +79304 + + + + + + +parent +idx + + +12 + + +1 +2 +1029801 + + +2 +3 +152799 + + +3 +6 +101382 + + +6 +20 +30211 + + + + + + +parent +bodydecl + + +12 + + +1 +2 +1314195 + + + + + + +idx +id + + +12 + + +1 +2 +290 + + +2 +3 +580 + + +3 +4 +580 + + +5 +6 +290 + + +7 +8 +290 + + +8 +9 +290 + + +10 +11 +290 + + +16 +17 +290 + + +24 +25 +290 + + +27 +28 +290 + + +40 +41 +290 + + +66 +67 +290 + + +87 +88 +290 + + +104 +105 +290 + + +172 +173 +290 + + +266 +267 +290 + + +657 +658 +290 + + +1632 +1633 +290 + + +3557 +3558 +290 + + + + + + +idx +kind + + +12 + + +1 +2 +871 + + +2 +3 +1161 + + +3 +4 +290 + + +4 +5 +871 + + +6 +7 +290 + + +8 +9 +580 + + +9 +10 +290 + + +10 +11 +580 + + +11 +12 +290 + + +13 +14 +290 + + +15 +16 +290 + + +18 +19 +290 + + + + + + +idx +parent + + +12 + + +1 +2 +290 + + +2 +3 +580 + + +3 +4 +580 + + +5 +6 +290 + + +7 +8 +290 + + +8 +9 +290 + + +10 +11 +290 + + +16 +17 +290 + + +24 +25 +290 + + +27 +28 +290 + + +40 +41 +290 + + +66 +67 +290 + + +87 +88 +290 + + +104 +105 +290 + + +172 +173 +290 + + +266 +267 +290 + + +657 +658 +290 + + +1632 +1633 +290 + + +3557 +3558 +290 + + + + + + +idx +bodydecl + + +12 + + +1 +2 +290 + + +2 +3 +580 + + +3 +4 +580 + + +5 +6 +290 + + +7 +8 +290 + + +8 +9 +290 + + +10 +11 +290 + + +16 +17 +290 + + +24 +25 +290 + + +26 +27 +290 + + +38 +39 +290 + + +60 +61 +290 + + +83 +84 +290 + + +96 +97 +290 + + +149 +150 +290 + + +221 +222 +290 + + +393 +394 +290 + + +572 +573 +290 + + +1193 +1194 +290 + + + + + + +bodydecl +id + + +12 + + +1 +2 +12781 + + +2 +3 +166743 + + +3 +4 +24982 + + +4 +5 +19753 + + +5 +7 +27887 + + +7 +9 +31663 + + +9 +13 +26144 + + +13 +25 +26725 + + +25 +76 +9876 + + + + + + +bodydecl +kind + + +12 + + +1 +2 +12781 + + +2 +3 +183882 + + +3 +4 +40669 + + +4 +5 +39507 + + +5 +6 +37764 + + +6 +9 +29920 + + +9 +11 +2033 + + + + + + +bodydecl +parent + + +12 + + +1 +2 +12781 + + +2 +3 +214965 + + +3 +4 +290 + + +4 +5 +38054 + + +5 +6 +11619 + + +6 +8 +29049 + + +8 +13 +27887 + + +13 +44 +11910 + + + + + + +bodydecl +idx + + +12 + + +1 +2 +179525 + + +2 +3 +42993 + + +3 +4 +52579 + + +4 +5 +23239 + + +5 +7 +29630 + + +7 +20 +18591 + + + + + + + + +exprs +6159056 + + +id +6159056 + + +kind +69 + + +typeid +47696 + + +parent +3894745 + + +idx +2325 + + + + +id +kind + + +12 + + +1 +2 +6159056 + + + + + + +id +typeid + + +12 + + +1 +2 +6159056 + + + + + + +id +parent + + +12 + + +1 +2 +6159056 + + + + + + +id +idx + + +12 + + +1 +2 +6159056 + + + + + + +kind +id + + +12 + + +15 +117 +6 + + +134 +442 +6 + + +448 +1626 +6 + + +1792 +2822 +6 + + +3929 +6964 +6 + + +7591 +11712 +6 + + +11834 +18822 +6 + + +19519 +25978 +6 + + +29030 +52946 +6 + + +52986 +97216 +6 + + +111763 +584003 +6 + + +610778 +1745966 +3 + + + + + + +kind +typeid + + +12 + + +1 +2 +18 + + +2 +3 +6 + + +3 +4 +5 + + +4 +5 +9 + + +5 +7 +5 + + +7 +16 +6 + + +62 +1086 +6 + + +1114 +2969 +6 + + +5935 +20138 +6 + + +21098 +37152 +2 + + + + + + +kind +parent + + +12 + + +15 +96 +6 + + +134 +442 +6 + + +448 +1626 +6 + + +1792 +2766 +6 + + +3749 +6944 +6 + + +7591 +11539 +6 + + +11687 +17039 +6 + + +18739 +25549 +6 + + +26413 +47972 +6 + + +49648 +70156 +6 + + +94269 +340490 +6 + + +585438 +1334459 +3 + + + + + + +kind +idx + + +12 + + +1 +2 +7 + + +2 +3 +2 + + +3 +4 +9 + + +4 +5 +5 + + +5 +6 +3 + + +6 +7 +6 + + +7 +8 +5 + + +8 +15 +6 + + +15 +20 +6 + + +20 +28 +6 + + +34 +247 +6 + + +257 +1108 +6 + + +1306 +2307 +2 + + + + + + +typeid +id + + +12 + + +1 +2 +9879 + + +2 +3 +7196 + + +3 +4 +3500 + + +4 +6 +4344 + + +6 +8 +3305 + + +8 +12 +4097 + + +12 +19 +3832 + + +19 +34 +3741 + + +34 +74 +3580 + + +74 +531 +3580 + + +531 +1199212 +642 + + + + + + +typeid +kind + + +12 + + +1 +2 +15522 + + +2 +3 +10351 + + +3 +4 +5542 + + +4 +5 +5522 + + +5 +6 +4138 + + +6 +7 +2777 + + +7 +11 +3724 + + +11 +40 +120 + + + + + + +typeid +parent + + +12 + + +1 +2 +9896 + + +2 +3 +7217 + + +3 +4 +3750 + + +4 +5 +2709 + + +5 +7 +3924 + + +7 +10 +3772 + + +10 +15 +3689 + + +15 +25 +3658 + + +25 +49 +3641 + + +49 +161 +3583 + + +161 +797514 +1857 + + + + + + +typeid +idx + + +12 + + +1 +2 +16167 + + +2 +3 +11252 + + +3 +4 +9003 + + +4 +5 +8633 + + +5 +2311 +2641 + + + + + + +parent +id + + +12 + + +1 +2 +2288191 + + +2 +3 +1412581 + + +3 +2307 +193973 + + + + + + +parent +kind + + +12 + + +1 +2 +2752763 + + +2 +3 +1062190 + + +3 +10 +79792 + + + + + + +parent +typeid + + +12 + + +1 +2 +3158374 + + +2 +3 +634790 + + +3 +51 +101581 + + + + + + +parent +idx + + +12 + + +1 +2 +2304958 + + +2 +3 +1396496 + + +3 +2307 +193291 + + + + + + +idx +id + + +12 + + +1 +6 +13 + + +8 +9 +258 + + +9 +10 +606 + + +10 +11 +156 + + +11 +12 +181 + + +12 +39 +175 + + +40 +89 +177 + + +89 +110 +178 + + +115 +161 +177 + + +161 +428 +176 + + +428 +956 +175 + + +960 +3458696 +53 + + + + + + +idx +kind + + +12 + + +1 +2 +1029 + + +2 +3 +186 + + +3 +4 +248 + + +4 +5 +322 + + +5 +6 +190 + + +6 +11 +212 + + +11 +68 +138 + + + + + + +idx +typeid + + +12 + + +1 +2 +1030 + + +2 +3 +185 + + +3 +4 +248 + + +4 +5 +317 + + +5 +7 +173 + + +7 +12 +190 + + +12 +531 +175 + + +895 +38165 +7 + + + + + + +idx +parent + + +12 + + +1 +6 +13 + + +8 +9 +258 + + +9 +10 +606 + + +10 +11 +156 + + +11 +12 +181 + + +12 +39 +175 + + +40 +89 +177 + + +89 +110 +178 + + +115 +161 +177 + + +161 +428 +176 + + +428 +956 +175 + + +960 +3440598 +53 + + + + + + + + +callableEnclosingExpr +6035785 + + +id +6035785 + + +callable_id +198248 + + + + +id +callable_id + + +12 + + +1 +2 +6035785 + + + + + + +callable_id +id + + +12 + + +1 +3 +17107 + + +3 +4 +13539 + + +4 +5 +15836 + + +5 +6 +25856 + + +6 +7 +10704 + + +7 +8 +9775 + + +8 +10 +15054 + + +10 +13 +17742 + + +13 +18 +15249 + + +18 +26 +15934 + + +26 +42 +15103 + + +42 +89 +15103 + + +89 +2632 +11241 + + + + + + + + +statementEnclosingExpr +5620332 + + +id +5620332 + + +statement_id +1192765 + + + + +id +statement_id + + +12 + + +1 +2 +5620332 + + + + + + +statement_id +id + + +12 + + +1 +2 +152574 + + +2 +3 +150707 + + +3 +4 +260769 + + +4 +5 +193260 + + +5 +6 +127526 + + +6 +7 +81653 + + +7 +8 +60015 + + +8 +11 +103626 + + +11 +3505 +62630 + + + + + + + + +callableBinding +1437798 + + +callerid +1437798 + + +callee +227012 + + + + +callerid +callee + + +12 + + +1 +2 +1437798 + + + + + + +callee +callerid + + +12 + + +1 +2 +119131 + + +2 +3 +37668 + + +3 +4 +17900 + + +4 +6 +17854 + + +6 +13 +17467 + + +13 +1437 +16989 + + + + + + + + +memberRefBinding +109 + + +id +109 + + +callable +85 + + + + +id +callable + + +12 + + +1 +2 +109 + + + + + + +callable +id + + +12 + + +1 +2 +73 + + +2 +3 +9 + + +3 +12 +3 + + + + + + + + +variableBinding +2012792 + + +expr +2012792 + + +variable +473626 + + + + +expr +variable + + +12 + + +1 +2 +2012792 + + + + + + +variable +expr + + +12 + + +1 +2 +170241 + + +2 +3 +100150 + + +3 +4 +70384 + + +4 +5 +37929 + + +5 +7 +33090 + + +7 +14 +35680 + + +14 +464 +26149 + + + + + + + + +localvars +269506 + + +id +269506 + + +nodeName +46158 + + +typeid +14855 + + +parentid +269506 + + + + +id +nodeName + + +12 + + +1 +2 +269506 + + + + + + +id +typeid + + +12 + + +1 +2 +269506 + + + + + + +id +parentid + + +12 + + +1 +2 +269506 + + + + + + +nodeName +id + + +12 + + +1 +2 +27837 + + +2 +3 +7375 + + +3 +4 +3093 + + +4 +8 +3909 + + +8 +79 +3462 + + +79 +4879 +480 + + + + + + +nodeName +typeid + + +12 + + +1 +2 +38703 + + +2 +3 +3918 + + +3 +44 +3465 + + +44 +201 +71 + + + + + + +nodeName +parentid + + +12 + + +1 +2 +27837 + + +2 +3 +7375 + + +3 +4 +3093 + + +4 +8 +3909 + + +8 +79 +3462 + + +79 +4879 +480 + + + + + + +typeid +id + + +12 + + +1 +2 +6310 + + +2 +3 +2516 + + +3 +4 +1293 + + +4 +5 +819 + + +5 +8 +1284 + + +8 +16 +1151 + + +16 +77 +1116 + + +77 +18404 +363 + + + + + + +typeid +nodeName + + +12 + + +1 +2 +8820 + + +2 +3 +2556 + + +3 +4 +1067 + + +4 +7 +1228 + + +7 +105 +1116 + + +105 +3085 +65 + + + + + + +typeid +parentid + + +12 + + +1 +2 +6310 + + +2 +3 +2516 + + +3 +4 +1293 + + +4 +5 +819 + + +5 +8 +1284 + + +8 +16 +1151 + + +16 +77 +1116 + + +77 +18404 +363 + + + + + + +parentid +id + + +12 + + +1 +2 +269506 + + + + + + +parentid +nodeName + + +12 + + +1 +2 +269506 + + + + + + +parentid +typeid + + +12 + + +1 +2 +269506 + + + + + + + + +namestrings +1876882 + + +name +53884 + + +value +51082 + + +parent +1876882 + + + + +name +value + + +12 + + +1 +2 +53884 + + + + + + +name +parent + + +12 + + +1 +2 +35919 + + +2 +3 +7891 + + +3 +5 +4137 + + +5 +16 +4102 + + +16 +99498 +1833 + + + + + + +value +name + + +12 + + +1 +2 +49205 + + +2 +10 +1876 + + + + + + +value +parent + + +12 + + +1 +2 +34680 + + +2 +3 +7428 + + +3 +6 +4408 + + +6 +46 +3840 + + +47 +99499 +724 + + + + + + +parent +name + + +12 + + +1 +2 +1876882 + + + + + + +parent +value + + +12 + + +1 +2 +1876882 + + + + + + + + +modules +72 + + +id +72 + + +name +72 + + + + +id +name + + +12 + + +1 +2 +72 + + + + + + +name +id + + +12 + + +1 +2 +72 + + + + + + + + +isOpen +5 + + +id +5 + + + + + +cumodule +30095 + + +fileId +30095 + + +moduleId +71 + + + + +fileId +moduleId + + +12 + + +1 +2 +30095 + + + + + + +moduleId +fileId + + +12 + + +2 +7 +6 + + +9 +14 +6 + + +14 +22 +6 + + +23 +33 +6 + + +34 +39 +6 + + +43 +59 +5 + + +65 +80 +6 + + +81 +131 +6 + + +149 +228 +6 + + +240 +471 +6 + + +540 +1680 +6 + + +1722 +5772 +6 + + + + + + + + +directives +890 + + +id +71 + + +directive +890 + + + + +id +directive + + +12 + + +2 +3 +8 + + +3 +4 +9 + + +4 +5 +12 + + +5 +6 +6 + + +6 +7 +7 + + +7 +8 +3 + + +8 +10 +6 + + +10 +12 +5 + + +12 +22 +6 + + +23 +54 +6 + + +57 +138 +3 + + + + + + +directive +id + + +12 + + +1 +2 +890 + + + + + + + + +requires +204 + + +id +204 + + +target +44 + + + + +id +target + + +12 + + +1 +2 +204 + + + + + + +target +id + + +12 + + +1 +2 +18 + + +2 +3 +9 + + +3 +4 +6 + + +4 +6 +3 + + +7 +9 +4 + + +10 +71 +4 + + + + + + + + +isTransitive +33 + + +id +33 + + + + + +isStatic +5 + + +id +5 + + + + + +exports +495 + + +id +495 + + +target +495 + + + + +id +target + + +12 + + +1 +2 +495 + + + + + + +target +id + + +12 + + +1 +2 +495 + + + + + + + + +exportsTo +365 + + +id +220 + + +target +57 + + + + +id +target + + +12 + + +1 +2 +152 + + +2 +3 +35 + + +3 +4 +21 + + +4 +22 +12 + + + + + + +target +id + + +12 + + +1 +2 +9 + + +2 +3 +15 + + +3 +4 +5 + + +4 +5 +5 + + +5 +6 +2 + + +6 +7 +4 + + +7 +8 +3 + + +8 +11 +4 + + +11 +15 +5 + + +17 +48 +5 + + + + + + + + +opens +15 + + +id +15 + + +target +15 + + + + +id +target + + +12 + + +1 +2 +15 + + + + + + +target +id + + +12 + + +1 +2 +15 + + + + + + + + +opensTo +15 + + +id +13 + + +target +7 + + + + +id +target + + +12 + + +1 +2 +12 + + +3 +4 +1 + + + + + + +target +id + + +12 + + +1 +2 +5 + + +2 +3 +1 + + +8 +9 +1 + + + + + + + + +uses +108 + + +id +108 + + +serviceInterface +108 + + + + +id +serviceInterface + + +12 + + +1 +2 +108 + + + + + + +serviceInterface +id + + +12 + + +1 +2 +108 + + + + + + + + +provides +68 + + +id +68 + + +serviceInterface +51 + + + + +id +serviceInterface + + +12 + + +1 +2 +68 + + + + + + +serviceInterface +id + + +12 + + +1 +2 +44 + + +2 +3 +4 + + +3 +9 +3 + + + + + + + + +providesWith +287 + + +id +68 + + +serviceImpl +285 + + + + +id +serviceImpl + + +12 + + +1 +2 +46 + + +2 +3 +8 + + +3 +4 +3 + + +4 +6 +5 + + +6 +109 +6 + + + + + + +serviceImpl +id + + +12 + + +1 +2 +283 + + +2 +3 +2 + + + + + + + + +javadoc +596965 + + +id +596965 + + + + + +isNormalComment +318427 + + +commentid +318427 + + + + + +isEolComment +279324 + + +commentid +279324 + + + + + +hasJavadoc +471181 + + +documentableid +420054 + + +javadocid +470309 + + + + +documentableid +javadocid + + +12 + + +1 +2 +380837 + + +2 +4 +36021 + + +4 +6 +3195 + + + + + + +javadocid +documentableid + + +12 + + +1 +2 +469728 + + +2 +4 +580 + + + + + + + + +javadocTag +1048974 + + +id +1048974 + + +name +2323 + + +parentid +342202 + + +idx +17429 + + + + +id +name + + +12 + + +1 +2 +1048974 + + + + + + +id +parentid + + +12 + + +1 +2 +1048974 + + + + + + +id +idx + + +12 + + +1 +2 +1048974 + + + + + + +name +id + + +12 + + +12 +13 +290 + + +53 +54 +290 + + +92 +93 +290 + + +142 +143 +290 + + +383 +384 +290 + + +531 +532 +290 + + +847 +848 +290 + + +1551 +1552 +290 + + + + + + +name +parentid + + +12 + + +12 +13 +290 + + +53 +54 +290 + + +92 +93 +290 + + +111 +112 +290 + + +382 +383 +290 + + +531 +532 +290 + + +561 +562 +290 + + +836 +837 +290 + + + + + + +name +idx + + +12 + + +5 +6 +290 + + +16 +17 +290 + + +30 +31 +290 + + +32 +33 +290 + + +34 +35 +290 + + +35 +36 +290 + + +36 +37 +290 + + +41 +42 +290 + + + + + + +parentid +id + + +12 + + +1 +2 +83662 + + +2 +3 +80757 + + +3 +4 +58389 + + +4 +5 +41540 + + +5 +6 +33406 + + +6 +7 +24111 + + +7 +11 +20334 + + + + + + +parentid +name + + +12 + + +1 +2 +103125 + + +2 +3 +123169 + + +3 +4 +70880 + + +4 +5 +38345 + + +5 +6 +6681 + + + + + + +parentid +idx + + +12 + + +1 +2 +83662 + + +2 +3 +80757 + + +3 +4 +58389 + + +4 +5 +41540 + + +5 +6 +33406 + + +6 +7 +24111 + + +7 +11 +20334 + + + + + + +idx +id + + +12 + + +1 +2 +5228 + + +2 +4 +1452 + + +4 +5 +1742 + + +5 +8 +1161 + + +8 +16 +1452 + + +18 +33 +1452 + + +36 +67 +1452 + + +73 +134 +1452 + + +184 +436 +1452 + + +522 +582 +580 + + + + + + +idx +name + + +12 + + +1 +2 +5519 + + +2 +3 +1742 + + +3 +4 +1742 + + +4 +5 +1161 + + +5 +6 +580 + + +6 +7 +2904 + + +7 +8 +2904 + + +8 +9 +871 + + + + + + +idx +parentid + + +12 + + +1 +2 +5228 + + +2 +4 +1452 + + +4 +5 +1742 + + +5 +8 +1161 + + +8 +16 +1452 + + +18 +33 +1452 + + +36 +67 +1452 + + +73 +134 +1452 + + +184 +436 +1452 + + +522 +582 +580 + + + + + + + + +javadocText +3965535 + + +id +3965535 + + +text +1438817 + + +parentid +1644487 + + +idx +54903 + + + + +id +text + + +12 + + +1 +2 +3965535 + + + + + + +id +parentid + + +12 + + +1 +2 +3965535 + + + + + + +id +idx + + +12 + + +1 +2 +3965535 + + + + + + +text +id + + +12 + + +1 +2 +974898 + + +2 +3 +244305 + + +3 +5 +123750 + + +5 +607 +95863 + + + + + + +text +parentid + + +12 + + +1 +2 +979836 + + +2 +3 +241110 + + +3 +5 +122588 + + +5 +486 +95282 + + + + + + +text +idx + + +12 + + +1 +2 +1352831 + + +2 +47 +85986 + + + + + + +parentid +id + + +12 + + +1 +2 +736983 + + +2 +3 +723620 + + +3 +12 +117940 + + +12 +190 +65942 + + + + + + +parentid +text + + +12 + + +1 +2 +736983 + + +2 +3 +723620 + + +3 +12 +122879 + + +12 +138 +61003 + + + + + + +parentid +idx + + +12 + + +1 +2 +736983 + + +2 +3 +723620 + + +3 +12 +117940 + + +12 +190 +65942 + + + + + + +idx +id + + +12 + + +1 +2 +31663 + + +2 +4 +3776 + + +4 +5 +4357 + + +5 +10 +4647 + + +10 +41 +4357 + + +46 +346 +4357 + + +384 +5662 +1742 + + + + + + +idx +text + + +12 + + +1 +2 +31663 + + +2 +4 +4066 + + +4 +5 +4938 + + +5 +11 +4357 + + +12 +33 +4357 + + +33 +185 +4357 + + +228 +2094 +1161 + + + + + + +idx +parentid + + +12 + + +1 +2 +31663 + + +2 +4 +3776 + + +4 +5 +4357 + + +5 +10 +4647 + + +10 +41 +4357 + + +46 +346 +4357 + + +384 +5662 +1742 + + + + + + + + +xmlEncoding +39724 + + +id +39724 + + +encoding +1 + + + + +id +encoding + + +12 + + +1 +2 +39724 + + + + + + +encoding +id + + +12 + + +39724 +39725 +1 + + + + + + + + +xmlDTDs +1 + + +id +1 + + +root +1 + + +publicId +1 + + +systemId +1 + + +fileid +1 + + + + +id +root + + +12 + + +1 +2 +1 + + + + + + +id +publicId + + +12 + + +1 +2 +1 + + + + + + +id +systemId + + +12 + + +1 +2 +1 + + + + + + +id +fileid + + +12 + + +1 +2 +1 + + + + + + +root +id + + +12 + + +1 +2 +1 + + + + + + +root +publicId + + +12 + + +1 +2 +1 + + + + + + +root +systemId + + +12 + + +1 +2 +1 + + + + + + +root +fileid + + +12 + + +1 +2 +1 + + + + + + +publicId +id + + +12 + + +1 +2 +1 + + + + + + +publicId +root + + +12 + + +1 +2 +1 + + + + + + +publicId +systemId + + +12 + + +1 +2 +1 + + + + + + +publicId +fileid + + +12 + + +1 +2 +1 + + + + + + +systemId +id + + +12 + + +1 +2 +1 + + + + + + +systemId +root + + +12 + + +1 +2 +1 + + + + + + +systemId +publicId + + +12 + + +1 +2 +1 + + + + + + +systemId +fileid + + +12 + + +1 +2 +1 + + + + + + +fileid +id + + +12 + + +1 +2 +1 + + + + + + +fileid +root + + +12 + + +1 +2 +1 + + + + + + +fileid +publicId + + +12 + + +1 +2 +1 + + + + + + +fileid +systemId + + +12 + + +1 +2 +1 + + + + + + + + +xmlElements +1270313 + + +id +1270313 + + +name +4655 + + +parentid +578021 + + +idx +35122 + + +fileid +39721 + + + + +id +name + + +12 + + +1 +2 +1270313 + + + + + + +id +parentid + + +12 + + +1 +2 +1270313 + + + + + + +id +idx + + +12 + + +1 +2 +1270313 + + + + + + +id +fileid + + +12 + + +1 +2 +1270313 + + + + + + +name +id + + +12 + + +1 +2 +420 + + +2 +5 +156 + + +5 +6 +3832 + + +6 +310317 +247 + + + + + + +name +parentid + + +12 + + +1 +2 +456 + + +2 +5 +150 + + +5 +6 +3829 + + +6 +161565 +220 + + + + + + +name +idx + + +12 + + +1 +2 +4358 + + +2 +35123 +297 + + + + + + +name +fileid + + +12 + + +1 +2 +486 + + +2 +5 +133 + + +5 +6 +3831 + + +6 +14503 +205 + + + + + + +parentid +id + + +12 + + +1 +2 +371969 + + +2 +3 +62095 + + +3 +4 +104113 + + +4 +35123 +39844 + + + + + + +parentid +name + + +12 + + +1 +2 +500482 + + +2 +3 +17866 + + +3 +4 +49117 + + +4 +45 +10556 + + + + + + +parentid +idx + + +12 + + +1 +2 +371969 + + +2 +3 +62095 + + +3 +4 +104113 + + +4 +35123 +39844 + + + + + + +parentid +fileid + + +12 + + +1 +2 +578021 + + + + + + +idx +id + + +12 + + +2 +3 +606 + + +4 +5 +17851 + + +5 +6 +6533 + + +6 +7 +859 + + +7 +8 +4471 + + +9 +16 +2719 + + +16 +578022 +2083 + + + + + + +idx +name + + +12 + + +1 +2 +18457 + + +2 +3 +6533 + + +3 +4 +6178 + + +4 +8 +2624 + + +8 +4397 +1330 + + + + + + +idx +parentid + + +12 + + +2 +3 +606 + + +4 +5 +17851 + + +5 +6 +6533 + + +6 +7 +859 + + +7 +8 +4471 + + +9 +16 +2719 + + +16 +578022 +2083 + + + + + + +idx +fileid + + +12 + + +2 +3 +606 + + +4 +5 +17851 + + +5 +6 +6533 + + +6 +7 +859 + + +7 +8 +4471 + + +9 +16 +2719 + + +16 +39722 +2083 + + + + + + +fileid +id + + +12 + + +1 +2 +20457 + + +2 +3 +3115 + + +3 +7 +3026 + + +7 +8 +3588 + + +8 +9 +2220 + + +9 +11 +3099 + + +11 +19 +3087 + + +19 +114506 +1129 + + + + + + +fileid +name + + +12 + + +1 +2 +20459 + + +2 +3 +3458 + + +3 +5 +2569 + + +5 +7 +2172 + + +7 +8 +6158 + + +8 +9 +3501 + + +9 +46 +1404 + + + + + + +fileid +parentid + + +12 + + +1 +2 +20457 + + +2 +3 +3870 + + +3 +5 +2152 + + +5 +6 +2876 + + +6 +7 +2720 + + +7 +8 +4132 + + +8 +14 +3096 + + +14 +31079 +418 + + + + + + +fileid +idx + + +12 + + +1 +2 +25894 + + +2 +3 +5301 + + +3 +4 +3787 + + +4 +6 +3268 + + +6 +35123 +1471 + + + + + + + + +xmlAttrs +1202020 + + +id +1202020 + + +elementid +760198 + + +name +3649 + + +value +121803 + + +idx +2000 + + +fileid +39448 + + + + +id +elementid + + +12 + + +1 +2 +1202020 + + + + + + +id +name + + +12 + + +1 +2 +1202020 + + + + + + +id +value + + +12 + + +1 +2 +1202020 + + + + + + +id +idx + + +12 + + +1 +2 +1202020 + + + + + + +id +fileid + + +12 + + +1 +2 +1202020 + + + + + + +elementid +id + + +12 + + +1 +2 +425697 + + +2 +3 +249659 + + +3 +4 +66474 + + +4 +2001 +18368 + + + + + + +elementid +name + + +12 + + +1 +2 +425778 + + +2 +3 +249579 + + +3 +4 +66475 + + +4 +2001 +18366 + + + + + + +elementid +value + + +12 + + +1 +2 +466237 + + +2 +3 +266291 + + +3 +46 +27670 + + + + + + +elementid +idx + + +12 + + +1 +2 +425697 + + +2 +3 +249659 + + +3 +4 +66474 + + +4 +2001 +18368 + + + + + + +elementid +fileid + + +12 + + +1 +2 +760198 + + + + + + +name +id + + +12 + + +1 +2 +3467 + + +2 +262475 +182 + + + + + + +name +elementid + + +12 + + +1 +2 +3467 + + +2 +262475 +182 + + + + + + +name +value + + +12 + + +1 +2 +3501 + + +2 +54146 +148 + + + + + + +name +idx + + +12 + + +1 +2 +3531 + + +2 +11 +118 + + + + + + +name +fileid + + +12 + + +1 +2 +3491 + + +2 +21768 +158 + + + + + + +value +id + + +12 + + +1 +2 +72032 + + +2 +3 +42366 + + +3 +199269 +7405 + + + + + + +value +elementid + + +12 + + +1 +2 +72036 + + +2 +3 +42374 + + +3 +199269 +7393 + + + + + + +value +name + + +12 + + +1 +2 +116722 + + +2 +2041 +5081 + + + + + + +value +idx + + +12 + + +1 +2 +117957 + + +2 +2001 +3846 + + + + + + +value +fileid + + +12 + + +1 +2 +86306 + + +2 +3 +28570 + + +3 +4175 +6927 + + + + + + +idx +id + + +12 + + +1 +2 +1955 + + +2 +760199 +45 + + + + + + +idx +elementid + + +12 + + +1 +2 +1955 + + +2 +760199 +45 + + + + + + +idx +name + + +12 + + +1 +2 +1955 + + +2 +189 +45 + + + + + + +idx +value + + +12 + + +1 +2 +1955 + + +2 +116643 +45 + + + + + + +idx +fileid + + +12 + + +1 +2 +1955 + + +2 +39449 +45 + + + + + + +fileid +id + + +12 + + +1 +2 +22884 + + +2 +4 +2565 + + +4 +6 +2294 + + +6 +7 +3299 + + +7 +9 +3272 + + +9 +16 +3143 + + +16 +129952 +1991 + + + + + + +fileid +elementid + + +12 + + +1 +2 +23890 + + +2 +4 +2131 + + +4 +5 +1971 + + +5 +6 +4096 + + +6 +8 +3519 + + +8 +16 +3137 + + +16 +106600 +704 + + + + + + +fileid +name + + +12 + + +1 +2 +22946 + + +2 +3 +2338 + + +3 +4 +2726 + + +4 +5 +2824 + + +5 +6 +2994 + + +6 +7 +3876 + + +7 +2002 +1744 + + + + + + +fileid +value + + +12 + + +1 +2 +22916 + + +2 +4 +2772 + + +4 +5 +2112 + + +5 +6 +3510 + + +6 +8 +1993 + + +8 +11 +3365 + + +11 +50357 +2780 + + + + + + +fileid +idx + + +12 + + +1 +2 +26133 + + +2 +3 +9699 + + +3 +5 +3511 + + +5 +2001 +105 + + + + + + + + +xmlNs +71201 + + +id +4185 + + +prefixName +958 + + +URI +4185 + + +fileid +39544 + + + + +id +prefixName + + +12 + + +1 +2 +2602 + + +2 +3 +1553 + + +3 +872 +30 + + + + + + +id +URI + + +12 + + +1 +2 +4185 + + + + + + +id +fileid + + +12 + + +1 +6 +274 + + +6 +7 +3825 + + +7 +24905 +86 + + + + + + +prefixName +id + + +12 + + +1 +2 +915 + + +2 +4054 +43 + + + + + + +prefixName +URI + + +12 + + +1 +2 +915 + + +2 +4054 +43 + + + + + + +prefixName +fileid + + +12 + + +1 +2 +828 + + +2 +5 +73 + + +5 +24903 +57 + + + + + + +URI +id + + +12 + + +1 +2 +4185 + + + + + + +URI +prefixName + + +12 + + +1 +2 +2602 + + +2 +3 +1553 + + +3 +872 +30 + + + + + + +URI +fileid + + +12 + + +1 +6 +274 + + +6 +7 +3825 + + +7 +24905 +86 + + + + + + +fileid +id + + +12 + + +1 +2 +11655 + + +2 +3 +26146 + + +3 +8 +1743 + + + + + + +fileid +prefixName + + +12 + + +1 +2 +11653 + + +2 +3 +25982 + + +3 +31 +1909 + + + + + + +fileid +URI + + +12 + + +1 +2 +11655 + + +2 +3 +26146 + + +3 +8 +1743 + + + + + + + + +xmlHasNs +1139730 + + +elementId +1139730 + + +nsId +4136 + + +fileid +39537 + + + + +elementId +nsId + + +12 + + +1 +2 +1139730 + + + + + + +elementId +fileid + + +12 + + +1 +2 +1139730 + + + + + + +nsId +elementId + + +12 + + +1 +5 +234 + + +5 +6 +3824 + + +6 +643289 +78 + + + + + + +nsId +fileid + + +12 + + +1 +5 +257 + + +5 +6 +3823 + + +6 +24759 +56 + + + + + + +fileid +elementId + + +12 + + +1 +2 +3669 + + +2 +3 +20429 + + +3 +7 +2536 + + +7 +8 +3473 + + +8 +9 +2258 + + +9 +11 +3036 + + +11 +18 +2966 + + +18 +147552 +1170 + + + + + + +fileid +nsId + + +12 + + +1 +2 +18261 + + +2 +3 +21032 + + +3 +8 +244 + + + + + + + + +xmlComments +26812 + + +id +26812 + + +text +22933 + + +parentid +26546 + + +fileid +26368 + + + + +id +text + + +12 + + +1 +2 +26812 + + + + + + +id +parentid + + +12 + + +1 +2 +26812 + + + + + + +id +fileid + + +12 + + +1 +2 +26812 + + + + + + +text +id + + +12 + + +1 +2 +21517 + + +2 +62 +1416 + + + + + + +text +parentid + + +12 + + +1 +2 +21519 + + +2 +62 +1414 + + + + + + +text +fileid + + +12 + + +1 +2 +21522 + + +2 +62 +1411 + + + + + + +parentid +id + + +12 + + +1 +2 +26379 + + +2 +17 +167 + + + + + + +parentid +text + + +12 + + +1 +2 +26379 + + +2 +17 +167 + + + + + + +parentid +fileid + + +12 + + +1 +2 +26546 + + + + + + +fileid +id + + +12 + + +1 +2 +26161 + + +2 +17 +207 + + + + + + +fileid +text + + +12 + + +1 +2 +26165 + + +2 +17 +203 + + + + + + +fileid +parentid + + +12 + + +1 +2 +26223 + + +2 +10 +145 + + + + + + + + +xmlChars +439958 + + +id +439958 + + +text +100518 + + +parentid +433851 + + +idx +4 + + +isCDATA +1 + + +fileid +26494 + + + + +id +text + + +12 + + +1 +2 +439958 + + + + + + +id +parentid + + +12 + + +1 +2 +439958 + + + + + + +id +idx + + +12 + + +1 +2 +439958 + + + + + + +id +isCDATA + + +12 + + +1 +2 +439958 + + + + + + +id +fileid + + +12 + + +1 +2 +439958 + + + + + + +text +id + + +12 + + +1 +2 +60389 + + +2 +4 +3811 + + +4 +5 +29257 + + +5 +23171 +7061 + + + + + + +text +parentid + + +12 + + +1 +2 +60389 + + +2 +4 +3811 + + +4 +5 +29257 + + +5 +23171 +7061 + + + + + + +text +idx + + +12 + + +1 +2 +100517 + + +2 +3 +1 + + + + + + +text +isCDATA + + +12 + + +1 +2 +100518 + + + + + + +text +fileid + + +12 + + +1 +2 +61284 + + +2 +4 +4205 + + +4 +5 +28328 + + +5 +351 +6701 + + + + + + +parentid +id + + +12 + + +1 +2 +429716 + + +2 +5 +4135 + + + + + + +parentid +text + + +12 + + +1 +2 +429716 + + +2 +5 +4135 + + + + + + +parentid +idx + + +12 + + +1 +2 +429716 + + +2 +5 +4135 + + + + + + +parentid +isCDATA + + +12 + + +1 +2 +433851 + + + + + + +parentid +fileid + + +12 + + +1 +2 +433851 + + + + + + +idx +id + + +12 + + +80 +81 +1 + + +1892 +1893 +1 + + +4135 +4136 +1 + + +433851 +433852 +1 + + + + + + +idx +text + + +12 + + +1 +2 +1 + + +3 +4 +1 + + +16 +17 +1 + + +100499 +100500 +1 + + + + + + +idx +parentid + + +12 + + +80 +81 +1 + + +1892 +1893 +1 + + +4135 +4136 +1 + + +433851 +433852 +1 + + + + + + +idx +isCDATA + + +12 + + +1 +2 +4 + + + + + + +idx +fileid + + +12 + + +4 +5 +1 + + +46 +47 +1 + + +97 +98 +1 + + +26494 +26495 +1 + + + + + + +isCDATA +id + + +12 + + +439958 +439959 +1 + + + + + + +isCDATA +text + + +12 + + +100518 +100519 +1 + + + + + + +isCDATA +parentid + + +12 + + +433851 +433852 +1 + + + + + + +isCDATA +idx + + +12 + + +4 +5 +1 + + + + + + +isCDATA +fileid + + +12 + + +26494 +26495 +1 + + + + + + +fileid +id + + +12 + + +1 +2 +25303 + + +2 +35123 +1191 + + + + + + +fileid +text + + +12 + + +1 +2 +25765 + + +2 +35123 +729 + + + + + + +fileid +parentid + + +12 + + +1 +2 +25312 + + +2 +35123 +1182 + + + + + + +fileid +idx + + +12 + + +1 +2 +26397 + + +2 +5 +97 + + + + + + +fileid +isCDATA + + +12 + + +1 +2 +26494 + + + + + + + + +xmllocations +3051056 + + +xmlElement +2982460 + + +location +3051056 + + + + +xmlElement +location + + +12 + + +1 +2 +2978326 + + +2 +24903 +4134 + + + + + + +location +xmlElement + + +12 + + +1 +2 +3051056 + + + + + + + + + diff --git a/java/ql/src/default.qll b/java/ql/src/default.qll new file mode 100644 index 00000000000..69104d35b85 --- /dev/null +++ b/java/ql/src/default.qll @@ -0,0 +1 @@ +import java diff --git a/java/ql/src/definitions.ql b/java/ql/src/definitions.ql new file mode 100644 index 00000000000..ab7a8d95993 --- /dev/null +++ b/java/ql/src/definitions.ql @@ -0,0 +1,185 @@ +/** + * @name Jump-to-definition links + * @description Generates use-definition pairs that provide the data + * for jump-to-definition in the code viewer. + * @kind definitions + * @id java/jump-to-definition + */ + +import java + +/** + * Restricts the location of a method access to the method identifier only, + * excluding its qualifier, type arguments and arguments. + * + * If there is any whitespace between the method identifier and its first argument, + * or between the method identifier and its qualifier (or last type argument, if any), + * the location may be slightly inaccurate and include such whitespace, + * but it should suffice for the purpose of avoiding overlapping definitions. + */ +class LocationOverridingMethodAccess extends MethodAccess { + override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { + exists(MemberRefExpr e | e.getReferencedCallable() = getMethod() | + exists(int elRef, int ecRef | + e.hasLocationInfo(path, _, _, elRef, ecRef) + | + sl = elRef and + sc = ecRef - getMethod().getName().length() + 1 and + el = elRef and + ec = ecRef + ) + ) or + not exists(MemberRefExpr e | e.getReferencedCallable() = getMethod()) and + exists(int slSuper, int scSuper, int elSuper, int ecSuper | + super.hasLocationInfo(path, slSuper, scSuper, elSuper, ecSuper) + | + ( + if (exists(getTypeArgument(_))) + then exists(Location locTypeArg | locTypeArg = getTypeArgument(count(getTypeArgument(_))-1).getLocation() | + sl = locTypeArg.getEndLine() and + sc = locTypeArg.getEndColumn()+2) + else ( + if exists(getQualifier()) + // Note: this needs to be the original (full) location of the qualifier, not the modified one. + then exists(Location locQual | locQual = getQualifier().getLocation() | + sl = locQual.getEndLine() and + sc = locQual.getEndColumn()+2) + else ( + sl = slSuper and + sc = scSuper + ) + ) + ) + and + ( + if (getNumArgument()>0) + // Note: this needs to be the original (full) location of the first argument, not the modified one. + then exists(Location locArg | locArg = getArgument(0).getLocation() | + el = locArg.getStartLine() and + ec = locArg.getStartColumn()-2 + ) else ( + el = elSuper and + ec = ecSuper-2 + ) + ) + ) + } +} + +/** + * Restricts the location of a type access to exclude + * the type arguments and qualifier, if any. + */ +class LocationOverridingTypeAccess extends TypeAccess { + override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { + exists(int slSuper, int scSuper, int elSuper, int ecSuper | + super.hasLocationInfo(path, slSuper, scSuper, elSuper, ecSuper) + | + ( + if exists(getQualifier()) + // Note: this needs to be the original (full) location of the qualifier, not the modified one. + then exists(Location locQual | locQual = getQualifier().getLocation() | + sl = locQual.getEndLine() and + sc = locQual.getEndColumn()+2) + else ( + sl = slSuper and + sc = scSuper + ) + ) + and + ( + if (exists(getTypeArgument(_))) + // Note: this needs to be the original (full) location of the first type argument, not the modified one. + then exists(Location locArg | locArg = getTypeArgument(0).getLocation() | + el = locArg.getStartLine() and + ec = locArg.getStartColumn()-2 + ) else ( + el = elSuper and + ec = ecSuper + ) + ) + ) + } +} + +/** + * Restricts the location of a field access to the name of the accessed field only, + * excluding its qualifier. + */ +class LocationOverridingFieldAccess extends FieldAccess { + override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { + super.hasLocationInfo(path, _, _, el, ec) and + sl = el and + sc = ec-(getField().getName().length())+1 + } +} + +/** + * Restricts the location of a single-type-import declaration to the name of the imported type only, + * excluding the `import` keyword and the package name. + */ +class LocationOverridingImportType extends ImportType { + override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { + exists(int slSuper, int scSuper, int elSuper, int ecSuper | + super.hasLocationInfo(path, slSuper, scSuper, elSuper, ecSuper) + | + el = elSuper and + ec = ecSuper-1 and + sl = el and + sc = ecSuper-(getImportedType().getName().length()) + ) + } +} + +/** + * Restricts the location of a single-static-import declaration to the name of the imported member(s) only, + * excluding the `import` keyword and the package name. + */ +class LocationOverridingImportStaticTypeMember extends ImportStaticTypeMember { + override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { + exists(int slSuper, int scSuper, int elSuper, int ecSuper | + super.hasLocationInfo(path, slSuper, scSuper, elSuper, ecSuper) + | + el = elSuper and + ec = ecSuper-1 and + sl = el and + sc = ecSuper-(getName().length()) + ) + } +} + +Element definition(Element e, string kind) { + e.(MethodAccess).getMethod().getSourceDeclaration() = result and kind = "M" + or + e.(TypeAccess).getType().(RefType).getSourceDeclaration() = result and kind = "T" + or + exists(Variable v | v = e.(VarAccess).getVariable() | + result = v.(Field).getSourceDeclaration() or + result = v.(Parameter).getSourceDeclaration() or + result = v.(LocalVariableDecl) + ) and kind = "V" + or + e.(ImportType).getImportedType() = result and kind = "I" + or + e.(ImportStaticTypeMember).getAMemberImport() = result and kind = "I" +} + +predicate dummyVarAccess(VarAccess va) { + exists(AssignExpr ae, InitializerMethod im | + ae.getDest() = va and + ae.getParent() = im.getBody().getAChild() + ) +} + +predicate dummyTypeAccess(TypeAccess ta) { + exists(FunctionalExpr e | e.getAnonymousClass().getClassInstanceExpr().getTypeName() = ta.getParent*()) +} + +from Element e, Element def, string kind +where + def = definition(e, kind) and + def.fromSource() and + e.fromSource() and + not dummyVarAccess(e) and + not dummyTypeAccess(e) +select e, def, kind diff --git a/java/ql/src/external/Clover.qll b/java/ql/src/external/Clover.qll new file mode 100644 index 00000000000..0e182450924 --- /dev/null +++ b/java/ql/src/external/Clover.qll @@ -0,0 +1,137 @@ +import java + +/** + * A Clover report is characterised by the fact that one of its + * top-level children (usually, in fact, there is only one) is + * a tag with the name "coverage". + */ +class CloverReport extends XMLFile { + CloverReport() { + this.getAChild().getName() = "coverage" + } +} + +/** + * The Clover "coverage" tag contains one or more "projects". + */ +class CloverCoverage extends XMLElement { + CloverCoverage() { + this.getParent() instanceof CloverReport and + this.getName() = "coverage" + } + + CloverProject getAProject() { + result = this.getAChild() + } +} + +/** + * Several elements in the Clover report contain a "metrics" element which + * contains various numbers, aggregated to the different levels. They are + * all subclasses of this class, to share code. + */ +abstract class CloverMetricsContainer extends XMLElement { + CloverMetrics getMetrics() { + result = this.getAChild() + } +} + +/** + * A "metrics" element contains a range of numbers for the current + * aggregation level. + */ +class CloverMetrics extends XMLElement { + CloverMetrics() { + this.getParent() instanceof CloverMetricsContainer and + this.getName() = "metrics" + } + + private + int attr(string name) { result = this.getAttribute(name).getValue().toInt() } + + private + float ratio(string name) { result = attr("covered" + name)/(float)attr(name) } + + int getNumConditionals() { result = attr("conditionals") } + int getNumCoveredConditionals() { result = attr("coveredconditionals") } + int getNumStatements() { result = attr("statements") } + int getNumCoveredStatements() { result = attr("coveredstatements") } + int getNumElements() { result = attr("elements") } + int getNumCoveredElements() { result = attr("coveredelements") } + int getNumMethods() { result = attr("methods") } + int getNumCoveredMethods() { result = attr("coveredmethods") } + int getNumLoC() { result = attr("loc") } + int getNumNonCommentedLoC() { result = attr("ncloc") } + int getNumPackages() { result = attr("packages") } + int getNumFiles() { result = attr("files") } + int getNumClasses() { result = attr("classes") } + int getCloverComplexity() { result = attr("complexity") } + + float getConditionalCoverage() { result = ratio("conditionals") } + float getStatementCoverage() { result = ratio("statements") } + float getElementCoverage() { result = ratio("elements") } + float getMethodCoverage() { result = ratio("methods") } + + float getNonCommentedLoCRatio() { result = attr("ncloc")/attr("loc")} +} + +/** + * A Clover project has an aggregated "metrics" element and + * groups together several "package" (or "testpackage") elements. + */ +class CloverProject extends CloverMetricsContainer { + CloverProject() { + this.getParent() instanceof CloverCoverage + } +} + +/** + * A Clover package is nested in a project and contains several files. + */ +class CloverPackage extends CloverMetricsContainer { + CloverPackage() { + this.getParent() instanceof CloverProject and + this.getName() = "package" + } + + Package getRealPackage() { + result.hasName(getAttribute("name").getValue()) + } +} + +/** + * A Clover file is nested in a package and contains several classes. + */ +class CloverFile extends CloverMetricsContainer { + CloverFile() { + this.getParent() instanceof CloverPackage and + this.getName() = "file" + } +} + +/** + * A Clover class is nested in a file and contains metric information. + */ +class CloverClass extends CloverMetricsContainer { + CloverClass() { + this.getParent() instanceof CloverFile and + this.getName() = "class" + } + + CloverPackage getPackage() { + result = ((CloverFile)getParent()).getParent() + } + + RefType getRealClass() { + result.hasQualifiedName(getPackage().getAttribute("name").getValue(), getAttribute("name").getValue()) + } +} + +/** + * Get the clover metrics associated with the given class, if any. + */ +CloverMetrics cloverInfo(RefType t) { + exists(CloverClass c | c.getRealClass() = t | + result = c.getMetrics() + ) +} diff --git a/java/ql/src/external/CodeDuplication.qll b/java/ql/src/external/CodeDuplication.qll new file mode 100644 index 00000000000..55571c6334a --- /dev/null +++ b/java/ql/src/external/CodeDuplication.qll @@ -0,0 +1,290 @@ +import java + +private +string relativePath(File file) { + result = file.getRelativePath().replaceAll("\\", "/") +} + +private cached +predicate tokenLocation(File file, int sl, int sc, int ec, int el, Copy copy, int index) { + file = copy.sourceFile() and + tokens(copy, index, sl, sc, ec, el) +} + +class Copy extends @duplication_or_similarity +{ + private + int lastToken() { + result = max(int i | tokens(this, i, _, _, _, _) | i) + } + + int tokenStartingAt(Location loc) { + tokenLocation(loc.getFile(), loc.getStartLine(), loc.getStartColumn(), _, _, this, result) + } + + int tokenEndingAt(Location loc) { + tokenLocation(loc.getFile(), _, _, loc.getEndLine(), loc.getEndColumn(), this, result) + } + + int sourceStartLine() { + tokens(this, 0, result, _, _, _) + } + + int sourceStartColumn() { + tokens(this, 0, _, result, _, _) + } + + int sourceEndLine() { + tokens(this, lastToken(), _, _, result, _) + } + + int sourceEndColumn() { + tokens(this, lastToken(), _, _, _, result) + } + + int sourceLines() { + result = this.sourceEndLine() + 1 - this.sourceStartLine() + } + + int getEquivalenceClass() { + duplicateCode(this, _, result) or similarCode(this, _, result) + } + + File sourceFile() { + exists(string name | duplicateCode(this, name, _) or similarCode(this, name, _) | + name.replaceAll("\\", "/") = relativePath(result) + ) + } + + predicate hasLocationInfo(string filepath, int startline, int startcolumn, int endline, int endcolumn) { + sourceFile().getAbsolutePath() = filepath and + startline = sourceStartLine() and + startcolumn = sourceStartColumn() and + endline = sourceEndLine() and + endcolumn = sourceEndColumn() + } + + string toString() { none() } +} + +class DuplicateBlock extends Copy, @duplication +{ + string toString() { + result = "Duplicate code: " + sourceLines() + " duplicated lines." + } +} + +class SimilarBlock extends Copy, @similarity +{ + string toString() { + result = "Similar code: " + sourceLines() + " almost duplicated lines." + } +} + +Method sourceMethod() { + hasLocation(result, _) and numlines(result, _, _, _) +} + +int numberOfSourceMethods(Class c) { + result = count(Method m | m = sourceMethod() and m.getDeclaringType() = c) +} + +private +predicate blockCoversStatement(int equivClass, int first, int last, Stmt stmt) { + exists(DuplicateBlock b, Location loc | + stmt.getLocation() = loc and + first = b.tokenStartingAt(loc) and + last = b.tokenEndingAt(loc) and + b.getEquivalenceClass() = equivClass + ) +} + +private +Stmt statementInMethod(Method m) { + result.getEnclosingCallable() = m and + not result instanceof Block +} + +private +predicate duplicateStatement(Method m1, Method m2, Stmt s1, Stmt s2) { + exists(int equivClass, int first, int last | + s1 = statementInMethod(m1) and + s2 = statementInMethod(m2) and + blockCoversStatement(equivClass, first, last, s1) and + blockCoversStatement(equivClass, first, last, s2) and + s1 != s2 and m1 != m2 + ) +} + +predicate duplicateStatements(Method m1, Method m2, int duplicate, int total) { + duplicate = strictcount(Stmt s | duplicateStatement(m1, m2, s, _)) and + total = strictcount(statementInMethod(m1)) +} + +/** + * Pairs of methods that are identical. + */ +predicate duplicateMethod(Method m, Method other) { + exists(int total | duplicateStatements(m, other, total, total)) +} + +predicate similarLines(File f, int line) { + exists(SimilarBlock b | + b.sourceFile() = f and line in [b.sourceStartLine() .. b.sourceEndLine()] + ) +} + +private +predicate similarLinesPerEquivalenceClass(int equivClass, int lines, File f) +{ + lines = strictsum(SimilarBlock b, int toSum | + (b.sourceFile() = f and b.getEquivalenceClass() = equivClass) and (toSum = b.sourceLines()) | toSum) +} + +private pragma[noopt] +predicate similarLinesCovered(File f, int coveredLines, File otherFile) { + exists(int numLines | numLines = f.getTotalNumberOfLines() | + exists(int coveredApprox | + coveredApprox = strictsum(int num | + exists(int equivClass | + similarLinesPerEquivalenceClass(equivClass, num, f) and + similarLinesPerEquivalenceClass(equivClass, num, otherFile) and + f != otherFile + ) + ) and + exists(int n, int product | + product = coveredApprox * 100 and n = product / numLines + | + n > 75 + ) + ) and + exists(int notCovered | + notCovered = count(int j | j in [1 .. numLines] and not similarLines(f, j)) and + coveredLines = numLines - notCovered + ) + ) +} + +predicate duplicateLines(File f, int line) { + exists(DuplicateBlock b | + b.sourceFile() = f and line in [b.sourceStartLine() .. b.sourceEndLine()] + ) +} + +private +predicate duplicateLinesPerEquivalenceClass(int equivClass, int lines, File f) +{ + lines = strictsum(DuplicateBlock b, int toSum | + (b.sourceFile() = f and b.getEquivalenceClass() = equivClass) and (toSum = b.sourceLines()) | toSum) +} + +private pragma[noopt] +predicate duplicateLinesCovered(File f, int coveredLines, File otherFile) { + exists(int numLines | numLines = f.getTotalNumberOfLines() | + exists(int coveredApprox | + coveredApprox = strictsum(int num | + exists(int equivClass | + duplicateLinesPerEquivalenceClass(equivClass, num, f) and + duplicateLinesPerEquivalenceClass(equivClass, num, otherFile) and + f != otherFile + ) + ) and + exists(int n, int product | product = coveredApprox * 100 and n = product / numLines | n > 75) + ) and + exists(int notCovered | + notCovered = count(int j | j in [1 .. numLines] and not duplicateLines(f, j)) and + coveredLines = numLines - notCovered + ) + ) +} + +predicate similarFiles(File f, File other, int percent) { + exists(int covered, int total | + similarLinesCovered(f, covered, other) and + total = f.getTotalNumberOfLines() and + covered * 100 / total = percent and + percent > 80 + ) and + not duplicateFiles(f, other, _) +} + +predicate duplicateFiles(File f, File other, int percent) { + exists(int covered, int total | + duplicateLinesCovered(f, covered, other) and + total = f.getTotalNumberOfLines() and + covered * 100 / total = percent and + percent > 70 + ) +} + +predicate duplicateAnonymousClass(AnonymousClass c, AnonymousClass other) { + exists(int numDup | + numDup = strictcount(Method m1 | + exists(Method m2 | + duplicateMethod(m1, m2) and + m1 = sourceMethod() and + m1.getDeclaringType() = c and + m2.getDeclaringType() = other and + c != other + ) + ) and + numDup = numberOfSourceMethods(c) and + numDup = numberOfSourceMethods(other) and + forall(Type t | c.getASupertype() = t | t = other.getASupertype()) + ) +} + +pragma[noopt] +predicate mostlyDuplicateClassBase(Class c, Class other, int numDup, int total) { + numDup = strictcount(Method m1 | + exists(Method m2 | + duplicateMethod(m1, m2) and + m1 = sourceMethod() and + m1.getDeclaringType() = c and + m2.getDeclaringType() = other and + other instanceof Class and + c != other + ) + ) and + total = numberOfSourceMethods(c) and + exists(int n, int product | product = 100 * numDup and n = product / total | n > 80) +} + +predicate mostlyDuplicateClass(Class c, Class other, string message) { + exists(int numDup, int total | + mostlyDuplicateClassBase(c, other, numDup, total) and + not c instanceof AnonymousClass and + not other instanceof AnonymousClass and + ( + ( + total != numDup and + exists(string s1, string s2, string s3, string name | + s1 = " out of " and s2 = " methods in " and s3 = " are duplicated in $@." and name = c.getName() + | + message = numDup + s1 + total + s2 + name + s3 + ) + ) + or + ( + total = numDup and + exists(string s1, string s2, string name | + s1 = "All methods in " and s2 = " are identical in $@." and name = c.getName() + | + message = s1 + name + s2 + ) + ) + ) + ) +} + +predicate fileLevelDuplication(File f, File other) { + similarFiles(f, other, _) or duplicateFiles(f, other, _) +} + +predicate classLevelDuplication(Class c, Class other) { + duplicateAnonymousClass(c, other) or mostlyDuplicateClass(c, other, _) +} + +predicate whitelistedLineForDuplication(File f, int line) { + exists(Import i | i.getFile() = f and i.getLocation().getStartLine() = line) +} diff --git a/java/ql/src/external/DefectFilter.qll b/java/ql/src/external/DefectFilter.qll new file mode 100644 index 00000000000..eb3588adc52 --- /dev/null +++ b/java/ql/src/external/DefectFilter.qll @@ -0,0 +1,51 @@ +/** Provides a class for working with defect query results stored in dashboard databases. */ + +import java + +/** + * Holds if `id` in the opaque identifier of a result reported by query `queryPath`, + * such that `message` is the associated message and the location of the result spans + * column `startcolumn` of line `startline` to column `endcolumn` of line `endline` + * in file `filepath`. + * + * For more information, see [LGTM locations](https://lgtm.com/help/ql/locations). + */ +external predicate defectResults(int id, string queryPath, + string file, int startline, int startcol, int endline, int endcol, + string message); + +/** + * A defect query result stored in a dashboard database. + */ +class DefectResult extends int { + DefectResult() { defectResults(this, _, _, _, _, _, _, _) } + + /** Gets the path of the query that reported the result. */ + string getQueryPath() { defectResults(this, result, _, _, _, _, _, _) } + + /** Gets the file in which this query result was reported. */ + File getFile() { + exists(string path | defectResults(this, _, path, _, _, _, _, _) | result.getAbsolutePath() = path) + } + + /** Gets the line on which the location of this query result starts. */ + int getStartLine() { defectResults(this, _, _, result, _, _, _, _) } + + /** Gets the column on which the location of this query result starts. */ + int getStartColumn() { defectResults(this, _, _, _, result, _, _, _) } + + /** Gets the line on which the location of this query result ends. */ + int getEndLine() { defectResults(this, _, _, _, _, result, _, _) } + + /** Gets the column on which the location of this query result ends. */ + int getEndColumn() { defectResults(this, _, _, _, _, _, result, _) } + + /** Gets the message associated with this query result. */ + string getMessage() { defectResults(this, _, _, _, _, _, _, result) } + + /** Gets the URL corresponding to the location of this query result. */ + string getURL() { + result = "file://" + getFile().getAbsolutePath() + ":" + + getStartLine() + ":" + getStartColumn() + ":" + getEndLine() + ":" + getEndColumn() + } +} diff --git a/java/ql/src/external/DuplicateAnonymous.java b/java/ql/src/external/DuplicateAnonymous.java new file mode 100644 index 00000000000..5181afbdf08 --- /dev/null +++ b/java/ql/src/external/DuplicateAnonymous.java @@ -0,0 +1,30 @@ +// BAD: Duplicate anonymous classes: +button1.addActionListener(new ActionListener() { + public void actionPerfored(ActionEvent e) + { + for (ActionListener listener: listeners) + listeners.actionPerformed(e); + } +}); + +button2.addActionListener(new ActionListener() { + public void actionPerfored(ActionEvent e) + { + for (ActionListener listener: listeners) + listeners.actionPerformed(e); + } +}); + +// ... and so on. + +// GOOD: Better solution: +class MultiplexingListener implements ActionListener { + public void actionPerformed(ActionEvent e) { + for (ActionListener listener : listeners) + listener.actionPerformed(e); + } +} + +button1.addActionListener(new MultiplexingListener()); +button2.addActionListener(new MultiplexingListener()); +// ... and so on. \ No newline at end of file diff --git a/java/ql/src/external/DuplicateAnonymous.qhelp b/java/ql/src/external/DuplicateAnonymous.qhelp new file mode 100644 index 00000000000..27679b3d274 --- /dev/null +++ b/java/ql/src/external/DuplicateAnonymous.qhelp @@ -0,0 +1,43 @@ + + + + + +

    Anonymous classes are a common way of creating implementations of an interface +or abstract class whose functionality is +really only needed once. Duplicating the definition of an anonymous class in several +places is usually a sign that refactoring is necessary.

    + +

    Code duplication in general is highly undesirable for a range of reasons. The artificially +inflated amount of code is more difficult to understand, and sequences of similar but subtly different lines +can mask the real purpose or intention behind them. Also, there is always a risk that only one +of several copies of the code is updated to address a defect or add a feature.

    + +
    + + +

    Introduce a concrete class +that contains the definition just once, and replace the anonymous classes with instances +of this concrete class.

    + +
    + + +

    In the following example, the definition of the class addActionListener is duplicated for +each button that needs to use it. A better solution is shown that defines just one class, +MultiplexingListener, which is used by each button.

    + + + +
    + + +
  • E. Juergens, F. Deissenboeck, B. Hummel, S. Wagner. +Do code clones matter? Proceedings of the 31st International Conference on +Software Engineering, +485-495, 2009.
  • + +
    +
    diff --git a/java/ql/src/external/DuplicateAnonymous.ql b/java/ql/src/external/DuplicateAnonymous.ql new file mode 100644 index 00000000000..76a6e83f588 --- /dev/null +++ b/java/ql/src/external/DuplicateAnonymous.ql @@ -0,0 +1,23 @@ +/** + * @name Duplicate anonymous class + * @description Duplicated anonymous classes indicate that refactoring is necessary. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/duplicate-anonymous-class + * @tags testability + * maintainability + * useless-code + * duplicate-code + * statistical + * non-attributable + */ +import java +import CodeDuplication + +from AnonymousClass c, AnonymousClass other +where + duplicateAnonymousClass(c, other) and + not fileLevelDuplication(c.getCompilationUnit(), other.getCompilationUnit()) +select c, "Anonymous class is identical to $@.", + other, "another anonymous class in " + other.getFile().getStem() diff --git a/java/ql/src/external/DuplicateBlock.ql b/java/ql/src/external/DuplicateBlock.ql new file mode 100644 index 00000000000..b8b36e318cc --- /dev/null +++ b/java/ql/src/external/DuplicateBlock.ql @@ -0,0 +1,21 @@ +/** + * @name Duplicate code + * @description This block of code is duplicated elsewhere. If possible, the shared code should be refactored so there is only one occurrence left. It may not always be possible to address these issues; other duplicate code checks (such as duplicate function, duplicate class) give subsets of the results with higher confidence. + * @kind problem + * @problem.severity recommendation + * @precision low + * @id java/duplicate-block + */ +import CodeDuplication + +from DuplicateBlock d, DuplicateBlock other, int lines, File otherFile, int otherLine +where + lines = d.sourceLines() and + lines > 10 and + other.getEquivalenceClass() = d.getEquivalenceClass() and + other != d and + otherFile = other.sourceFile() and + otherLine = other.sourceStartLine() +select + d, + "Duplicate code: " + lines + " lines are duplicated at " + otherFile.getStem() + ":" + otherLine + "." diff --git a/java/ql/src/external/DuplicateMethod.java b/java/ql/src/external/DuplicateMethod.java new file mode 100644 index 00000000000..bd85d8eb1db --- /dev/null +++ b/java/ql/src/external/DuplicateMethod.java @@ -0,0 +1,23 @@ +class RowWidget extends Widget { + // ... + public void collectChildren(Set result) { + for (Widget child : this.children) { + if (child.isVisible()) { + result.add(children); + child.collectChildren(result); + } + } + } +} + +class ColumnWidget extends Widget { + // ... + public void collectChildren(Set result) { + for (Widget child : this.children) { + if (child.isVisible()) { + result.add(children); + child.collectChildren(result); + } + } + } +} \ No newline at end of file diff --git a/java/ql/src/external/DuplicateMethod.qhelp b/java/ql/src/external/DuplicateMethod.qhelp new file mode 100644 index 00000000000..cf1a173db10 --- /dev/null +++ b/java/ql/src/external/DuplicateMethod.qhelp @@ -0,0 +1,62 @@ + + + + + +

    A method should never be duplicated exactly in several places in the code. +The severity of this problem is higher for longer methods than for extremely short +methods of one or two statements, but there are usually better ways of achieving the same +effect.

    + +

    Code duplication in general is highly undesirable for a range of reasons. The artificially +inflated amount of code is more difficult to understand, and sequences of similar but subtly different lines +can mask the real purpose or intention behind them. Also, there is always a risk that only one +of several copies of the code is updated to address a defect or add a feature.

    + +
    + + +

    At its simplest, the duplication can be addressed by simply removing all but one of the duplicate +method definitions, and changing calls to the removed methods so that they call the remaining function +instead.

    + +

    This may not be possible because of visibility or accessibility. A common example is +where two classes implement the same functionality but neither is a subtype of the other, +so it is not possible to inherit a single method definition. In such cases, introducing a +common superclass to share the duplicated code is a possible option. Alternatively, if the methods +do not need access to private object state, they can be moved to a shared utility class that +just provides the functionality itself.

    + +
    + + +

    In the following example, RowWidget and ColumnWidget contain duplicate +methods. The collectChildren method should probably be moved into the +superclass, Widget, and shared between RowWidget and +ColumnWidget.

    + + + +

    Alternatively, if not all kinds of Widget actually need collectChildren +(for example, not all of them have children), it might be necessary to introduce +a new, possibly abstract, class under Widget. For example, the new class might be called +ContainerWidget and include a single definition of collectChildren. Both +RowWidget and ColumnWidget could extend the class and inherit +collectChildren.

    + +

    Modern IDEs may provide refactoring support for this sort of issue, usually +with the names "Pull up" or "Extract supertype".

    + +
    + + +
  • E. Juergens, F. Deissenboeck, B. Hummel, S. Wagner. +Do code clones matter? Proceedings of the 31st International Conference on +Software Engineering, +485-495, 2009.
  • + + +
    +
    diff --git a/java/ql/src/external/DuplicateMethod.ql b/java/ql/src/external/DuplicateMethod.ql new file mode 100644 index 00000000000..6084cf89c3c --- /dev/null +++ b/java/ql/src/external/DuplicateMethod.ql @@ -0,0 +1,31 @@ +/** + * @name Duplicate method + * @description Duplicated methods make code more difficult to understand and introduce a risk of + * changes being made to only one copy. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/duplicate-method + * @tags testability + * maintainability + * useless-code + * duplicate-code + * statistical + * non-attributable + */ +import java +import CodeDuplication + +predicate relevant(Method m) { + m.getNumberOfLinesOfCode() > 5 and not m.getName().matches("get%") or + m.getNumberOfLinesOfCode() > 10 +} + +from Method m, Method other +where + duplicateMethod(m, other) and + relevant(m) and + not fileLevelDuplication(m.getCompilationUnit(), other.getCompilationUnit()) and + not classLevelDuplication(m.getDeclaringType(), other.getDeclaringType()) +select m, "Method " + m.getName() + " is duplicated in $@.", + other, other.getDeclaringType().getQualifiedName() diff --git a/java/ql/src/external/ExternalArtifact.qll b/java/ql/src/external/ExternalArtifact.qll new file mode 100644 index 00000000000..233dcf24f13 --- /dev/null +++ b/java/ql/src/external/ExternalArtifact.qll @@ -0,0 +1,44 @@ +import java + +class ExternalData extends @externalDataElement { + + string getDataPath() { externalData(this, result, _, _) } + + string getQueryPath() { result = getDataPath().regexpReplaceAll("\\.[^.]*$", ".ql") } + + int getNumFields() { result = 1 + max(int i | externalData(this, _, i, _) | i) } + + string getField(int index) { externalData(this, _, index, result) } + + int getFieldAsInt(int index) { result = getField(index).toInt() } + + float getFieldAsFloat(int index) { result = getField(index).toFloat() } + + date getFieldAsDate(int index) { result = getField(index).toDate() } + + string toString() { + result = getQueryPath() + ": " + buildTupleString(0) + } + + private string buildTupleString(int start) { + (start = getNumFields() - 1 and result = getField(start)) + or + (start < getNumFields() - 1 and result = getField(start) + "," + buildTupleString(start+1)) + } + +} + +/** + * External data with a location, and a message, as produced by tools that used to produce QLDs. + */ +class DefectExternalData extends ExternalData { + DefectExternalData() { + this.getField(0).regexpMatch("\\w+://.*:[0-9]+:[0-9]+:[0-9]+:[0-9]+$") and + this.getNumFields() = 2 + } + + string getURL() { result = getField(0) } + + string getMessage() { result = getField(1) } +} + diff --git a/java/ql/src/external/MetricFilter.qll b/java/ql/src/external/MetricFilter.qll new file mode 100644 index 00000000000..d8f4724d4f6 --- /dev/null +++ b/java/ql/src/external/MetricFilter.qll @@ -0,0 +1,37 @@ +import java + +external predicate metricResults(int id, string queryPath, string file, int startline, int startcol, int endline, int endcol, float value); + +class MetricResult extends int { + + MetricResult() { metricResults(this, _, _, _, _, _, _, _) } + + string getQueryPath() { metricResults(this, result, _, _, _, _, _, _) } + + File getFile() { exists(string path | metricResults(this, _, path, _, _, _, _, _) and result.getAbsolutePath() = path) } + + int getStartLine() { metricResults(this, _, _, result, _, _, _, _) } + + int getStartColumn() { metricResults(this, _, _, _, result, _, _, _) } + + int getEndLine() { metricResults(this, _, _, _, _, result, _, _) } + + int getEndColumn() { metricResults(this, _, _, _, _, _, result, _) } + + predicate hasMatchingLocation() { exists(this.getMatchingLocation()) } + + Location getMatchingLocation() { + result.getFile() = this.getFile() and + result.getStartLine() = this.getStartLine() and + result.getEndLine() = this.getEndLine() and + result.getStartColumn() = this.getStartColumn() and + result.getEndColumn() = this.getEndColumn() + } + + float getValue() { metricResults(this, _, _, _, _, _, _, result) } + + string getURL() { + result = "file://" + getFile().getAbsolutePath() + ":" + getStartLine() + ":" + getStartColumn() + ":" + getEndLine() + ":" + getEndColumn() + } + +} diff --git a/java/ql/src/external/MostlyDuplicateClass.qhelp b/java/ql/src/external/MostlyDuplicateClass.qhelp new file mode 100644 index 00000000000..a3c056b3a4f --- /dev/null +++ b/java/ql/src/external/MostlyDuplicateClass.qhelp @@ -0,0 +1,43 @@ + + + + + +

    When most of the methods in one class are duplicated in one or more other classes, +the classes themselves are regarded as mostly duplicate.

    + +

    Code duplication in general is highly undesirable for a range of reasons. The artificially +inflated amount of code is more difficult to understand, and sequences of similar but subtly different lines +can mask the real purpose or intention behind them. Also, there is always a risk that only one +of several copies of the code is updated to address a defect or add a feature.

    + +
    + +

    Although completely duplicated classes are rare, they are usually a sign of a simple +oversight (or deliberate copy/paste) by a developer. Usually the required solution +is to remove all but one of them.

    + +

    It is more common to see duplication of many methods between two classes, leaving just +a few that are actually different. Decide whether the differences are +intended or the result of an inconsistent update to one of the copies:

    +
      +
    • If the two classes serve different purposes but many of their methods are duplicated, this indicates +that there is a missing level of abstraction. Introducing a common super-class to define the +common methods is likely to prevent many problems in the long term. Modern IDEs may provide +refactoring support for this sort of issue, usually with the names "Pull up" or "Extract supertype".
    • +
    • If the two classes serve the same purpose and are different only as a result of inconsistent updates +then treat the classes as completely duplicate. Determine +the most up-to-date and correct version of the code and eliminate all near duplicates.
    + +
    + + +
  • E. Juergens, F. Deissenboeck, B. Hummel, S. Wagner. +Do code clones matter? Proceedings of the 31st International Conference on +Software Engineering, +485-495, 2009.
  • + +
    +
    diff --git a/java/ql/src/external/MostlyDuplicateClass.ql b/java/ql/src/external/MostlyDuplicateClass.ql new file mode 100644 index 00000000000..68734f314ee --- /dev/null +++ b/java/ql/src/external/MostlyDuplicateClass.ql @@ -0,0 +1,23 @@ +/** + * @name Mostly duplicate class + * @description Classes in which most of the methods are duplicated in another class make code more + * difficult to understand and introduce a risk of changes being made to only one copy. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/duplicate-class + * @tags testability + * maintainability + * useless-code + * duplicate-code + * statistical + * non-attributable + */ +import java +import CodeDuplication + +from Class c, string message, Class link +where + mostlyDuplicateClass(c, link, message) and + not fileLevelDuplication(c.getCompilationUnit(), _) +select c, message, link, link.getQualifiedName() diff --git a/java/ql/src/external/MostlyDuplicateFile.qhelp b/java/ql/src/external/MostlyDuplicateFile.qhelp new file mode 100644 index 00000000000..a2721c0f157 --- /dev/null +++ b/java/ql/src/external/MostlyDuplicateFile.qhelp @@ -0,0 +1,44 @@ + + + + + +

    When most of the lines in one file are duplicated in one or more other +files, the files themselves are regarded as mostly duplicate.

    + +

    Code duplication in general is highly undesirable for a range of reasons. The artificially +inflated amount of code is more difficult to understand, and sequences of similar but subtly different lines +can mask the real purpose or intention behind them. Also, there is always a risk that only one +of several copies of the code is updated to address a defect or add a feature.

    + +
    + +

    Although completely duplicated files are rare, they are usually a sign of a simple +oversight (or deliberate copy/paste) by a developer. Usually the required solution +is to remove all but one of them. A common exception is generated code +that simply occurs in several places in the source tree.

    + +

    It is more common to see duplication of many lines between two files, leaving just +a few that are actually different. Decide whether the differences are +intended or the result of an inconsistent update to one of the copies:

    +
      +
    • If the two files serve different purposes but many of their lines are duplicated, this indicates +that there is a missing level of abstraction. Look for ways to share the functionality, either +by extracting a utility class for parts of it or by encapsulating the common parts into a new +super class of any classes involved.
    • +
    • If the two files serve the same purpose and are different only as a result of inconsistent updates +then treat the files as completely duplicate. Determine +the most up-to-date and correct version of the code and eliminate all near duplicates.
    + +
    + + +
  • E. Juergens, F. Deissenboeck, B. Hummel, S. Wagner. +Do code clones matter? Proceedings of the 31st International Conference on +Software Engineering, +485-495, 2009.
  • + +
    +
    diff --git a/java/ql/src/external/MostlyDuplicateFile.ql b/java/ql/src/external/MostlyDuplicateFile.ql new file mode 100644 index 00000000000..2a339830839 --- /dev/null +++ b/java/ql/src/external/MostlyDuplicateFile.ql @@ -0,0 +1,22 @@ +/** + * @name Mostly duplicate file + * @description Files in which most of the lines are duplicated in another file make code more + * difficult to understand and introduce a risk of changes being made to only one copy. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/duplicate-file + * @tags testability + * maintainability + * useless-code + * duplicate-code + * statistical + * non-attributable + */ +import java +import CodeDuplication + +from File f, File other, int percent +where duplicateFiles(f, other, percent) +select f, percent + "% of the lines in " + f.getStem() + " are copies of lines in $@.", + other, other.getStem() diff --git a/java/ql/src/external/MostlyDuplicateMethod.qhelp b/java/ql/src/external/MostlyDuplicateMethod.qhelp new file mode 100644 index 00000000000..8388520189d --- /dev/null +++ b/java/ql/src/external/MostlyDuplicateMethod.qhelp @@ -0,0 +1,48 @@ + + + + +

    When most of the lines in one method are duplicated in one or more other +methods, the methods themselves are regarded as mostly duplicate or similar.

    + +

    Code duplication in general is highly undesirable for a range of reasons. The artificially +inflated amount of code is more difficult to understand, and sequences of similar but subtly different lines +can mask the real purpose or intention behind them. Also, there is always a risk that only one +of several copies of the code is updated to address a defect or add a feature.

    + +
    + +

    Although completely duplicated methods are rare, they are usually a sign of a simple +oversight (or deliberate copy/paste) by a developer. Usually the required solution +is to remove all but one of them.

    + +

    It is more common to see duplication of many lines between two methods, leaving just +a few that are actually different. Decide whether the differences are +intended or the result of an inconsistent update to one of the copies.

    +
      +
    • If the two methods serve different purposes but many of their lines are duplicated, this indicates +that there is a missing level of abstraction. Look for ways of encapsulating the commonality and sharing it while +retaining the differences in functionality. Perhaps the method can be moved to a single place +and given an additional parameter, allowing it to cover all use cases. Alternatively, there +may be a common pre-processing or post-processing step that can be extracted to its own (shared) +method, leaving only the specific parts in the existing methods. Modern IDEs may provide +refactoring support for this sort of issue, usually with the names "Extract method", "Change method signature", +"Pull up" or "Extract supertype".
    • +
    • If the two methods serve the same purpose and are different only as a result of inconsistent updates +then treat the methods as completely duplicate. Determine +the most up-to-date and correct version of the code and eliminate all near duplicates. Callers of the +removed methods should be updated to call the remaining method instead.
    + +
    + + +
  • E. Juergens, F. Deissenboeck, B. Hummel, S. Wagner. +Do code clones matter? Proceedings of the 31st International Conference on +Software Engineering, +485-495, 2009.
  • + + +
    +
    diff --git a/java/ql/src/external/MostlyDuplicateMethod.ql b/java/ql/src/external/MostlyDuplicateMethod.ql new file mode 100644 index 00000000000..53fb1271b05 --- /dev/null +++ b/java/ql/src/external/MostlyDuplicateMethod.ql @@ -0,0 +1,30 @@ +/** + * @name Mostly duplicate method + * @description Methods in which most of the lines are duplicated in another method make code more + * difficult to understand and introduce a risk of changes being made to only one copy. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/similar-method + * @tags testability + * maintainability + * useless-code + * duplicate-code + * statistical + * non-attributable + */ +import java +import CodeDuplication + +from Method m, int covered, int total, Method other, int percent +where + duplicateStatements(m, other, covered, total) and + covered != total and + m.getMetrics().getNumberOfLinesOfCode() > 5 and + covered * 100 / total = percent and + percent > 80 and + not duplicateMethod(m, other) and + not classLevelDuplication(m.getDeclaringType(), other.getDeclaringType()) and + not fileLevelDuplication(m.getCompilationUnit(), other.getCompilationUnit()) +select m, percent + "% of the statements in " + m.getName() + " are duplicated in $@.", + other, other.getDeclaringType().getName() + "." + other.getStringSignature() diff --git a/java/ql/src/external/MostlySimilarFile.qhelp b/java/ql/src/external/MostlySimilarFile.qhelp new file mode 100644 index 00000000000..fcea9164d8d --- /dev/null +++ b/java/ql/src/external/MostlySimilarFile.qhelp @@ -0,0 +1,39 @@ + + + + + +

    When most of the lines in one file have corresponding "similar" lines in one or more other +files, the files themselves are regarded as mostly similar. Two lines are defined as +similar if they are either identical or contain only very minor differences.

    + +

    Code duplication in general is highly undesirable for a range of reasons. The artificially +inflated amount of code is more difficult to understand, and sequences of similar but subtly different lines +can mask the real purpose or intention behind them. Also, there is always a risk that only one +of several copies of the code is updated to address a defect or add a feature.

    + +
    + + +

    Consider whether the differences are +deliberate or a result of an inconsistent update to one of the clones. If the latter, then +treating the files as completely duplicate and eliminating all but one (while preserving any +corrections or new features that may have been introduced) is the best course. If two files serve +genuinely different purposes but almost all of their lines are the same, that can be a sign +that there is a missing level of abstraction. Can some of the shared code be extracted into +methods (perhaps with additional parameters, to cover the differences in behavior)? Should it +be moved into a utility class or file that is accessible to all current implementations, or +should a new level of abstraction be introduced?

    + +
    + + +
  • E. Juergens, F. Deissenboeck, B. Hummel, S. Wagner. +Do code clones matter? Proceedings of the 31st International Conference on +Software Engineering, +485-495, 2009.
  • + +
    +
    diff --git a/java/ql/src/external/MostlySimilarFile.ql b/java/ql/src/external/MostlySimilarFile.ql new file mode 100644 index 00000000000..70bd6e68ce4 --- /dev/null +++ b/java/ql/src/external/MostlySimilarFile.ql @@ -0,0 +1,22 @@ +/** + * @name Mostly similar file + * @description Files in which most of the lines are similar to those in another file make code more + * difficult to understand and introduce a risk of changes being made to only one copy. + * @kind problem + * @problem.severity recommendation + * @precision high + * @id java/similar-file + * @tags testability + * maintainability + * useless-code + * duplicate-code + * statistical + * non-attributable + */ +import java +import CodeDuplication + +from File f, File other, int percent +where similarFiles(f, other, percent) +select f, percent + "% of the lines in " + f.getStem() + " are similar to lines in $@.", + other, other.getStem() diff --git a/java/ql/src/external/VCS.qll b/java/ql/src/external/VCS.qll new file mode 100644 index 00000000000..b3903d8e7e7 --- /dev/null +++ b/java/ql/src/external/VCS.qll @@ -0,0 +1,90 @@ +import java + +class Commit extends @svnentry { + + Commit() { + svnaffectedfiles(this, _, _) and + exists(date svnDate, date snapshotDate | + svnentries(this, _, _, svnDate, _) and + snapshotDate(snapshotDate) and + svnDate <= snapshotDate + ) + } + + string toString() { result = this.getRevisionName() } + + string getRevisionName() { svnentries(this, result, _, _, _) } + + string getAuthor() { svnentries(this, _, result, _, _) } + + date getDate() { svnentries(this, _, _, result, _) } + + int getChangeSize() { svnentries(this, _, _, _, result) } + + string getMessage() { svnentrymsg(this, result) } + + string getAnAffectedFilePath(string action) { + exists(File rawFile | svnaffectedfiles(this, rawFile, action) | + result = rawFile.getAbsolutePath() + ) + } + + string getAnAffectedFilePath() { result = getAnAffectedFilePath(_) } + + File getAnAffectedFile(string action) { + svnaffectedfiles(this,result,action) + } + + File getAnAffectedFile() { exists(string action | result = this.getAnAffectedFile(action)) } + + predicate isRecent() { recentCommit(this) } + + int daysToNow() { + exists(date now | snapshotDate(now) | + result = getDate().daysTo(now) and result >= 0 + ) + } + + int getRecentAdditionsForFile(File f) { + svnchurn(this, f, result, _) + } + + int getRecentDeletionsForFile(File f) { + svnchurn(this, f, _, result) + } + + int getRecentChurnForFile(File f) { + exists(int added, int deleted | svnchurn(this, f, added, deleted) and result = added+deleted) + } +} + +class Author extends string { + Author() { exists(Commit e | this = e.getAuthor()) } + + Commit getACommit() { result.getAuthor() = this } + + File getAnEditedFile() { result = this.getACommit().getAnAffectedFile() } +} + +predicate recentCommit(Commit e) { + exists(date snapshotDate, date commitDate, int days | + snapshotDate(snapshotDate) and + e.getDate() = commitDate and + days = commitDate.daysTo(snapshotDate) and + days >= 0 and days <= 60 + ) +} + +date firstChange(File f) { + result = min(Commit e, date toMin | f = e.getAnAffectedFile() and toMin = e.getDate() | toMin) +} + +predicate firstCommit(Commit e) { + not exists(File f | f = e.getAnAffectedFile() | + firstChange(f) < e.getDate() + ) +} + +predicate artificialChange(Commit e) { + firstCommit(e) or e.getChangeSize() >= 50000 +} diff --git a/java/ql/src/filters/ClassifyFiles.ql b/java/ql/src/filters/ClassifyFiles.ql new file mode 100644 index 00000000000..080f4530a43 --- /dev/null +++ b/java/ql/src/filters/ClassifyFiles.ql @@ -0,0 +1,20 @@ +/** + * @name Classify files + * @description This query produces a list of all files in a snapshot + * that are classified as generated code or test code. + * @kind file-classifier + * @id java/file-classifier + */ + +import java + +predicate classify(File f, string tag) { + f instanceof GeneratedFile and tag = "generated" or + exists(GeneratedClass gc | gc.getFile() = f | tag = "generated") or + exists(TestClass tc | tc.getFile() = f | tag = "test") or + exists(TestMethod tm | tm.getFile() = f | tag = "test") +} + +from File f, string tag +where classify(f, tag) +select f, tag diff --git a/java/ql/src/filters/FromSource.ql b/java/ql/src/filters/FromSource.ql new file mode 100644 index 00000000000..a2fbd760763 --- /dev/null +++ b/java/ql/src/filters/FromSource.ql @@ -0,0 +1,14 @@ +/** + * @name Filter: only keep results from source + * @description Shows how to filter for only certain files + * @kind problem + * @id java/source-filter + */ +import java +import external.DefectFilter + +from DefectResult res, CompilationUnit cu +where + cu = res.getFile() and + cu.fromSource() +select res, res.getMessage() diff --git a/java/ql/src/filters/ImportAdditionalLibraries.ql b/java/ql/src/filters/ImportAdditionalLibraries.ql new file mode 100644 index 00000000000..d3a5f8bc52a --- /dev/null +++ b/java/ql/src/filters/ImportAdditionalLibraries.ql @@ -0,0 +1,17 @@ +/** + * @name (Import additional libraries) + * @description This query produces no results but imports some libraries we + * would like to make available in the LGTM query console even + * if they are not used by any queries. + * @kind file-classifier + * @id java/lgtm/import-additional-libraries + */ + +import java + +import semmle.code.java.dataflow.Guards +import semmle.code.java.security.DataFlow + +from File f, string tag +where none() +select f, tag diff --git a/java/ql/src/filters/NotGenerated.ql b/java/ql/src/filters/NotGenerated.ql new file mode 100644 index 00000000000..967ec2be72d --- /dev/null +++ b/java/ql/src/filters/NotGenerated.ql @@ -0,0 +1,12 @@ +/** + * @name Filter: non-generated files + * @description Only keep results that aren't in generated files + * @kind problem + * @id java/not-generated-file-filter + */ +import java +import external.DefectFilter + +from DefectResult res +where not res.getFile() instanceof GeneratedFile +select res, res.getMessage() diff --git a/java/ql/src/filters/NotGeneratedForMetric.ql b/java/ql/src/filters/NotGeneratedForMetric.ql new file mode 100644 index 00000000000..198b9a283d5 --- /dev/null +++ b/java/ql/src/filters/NotGeneratedForMetric.ql @@ -0,0 +1,12 @@ +/** + * @name Metric Filter: non-generated files + * @description Only keep metric results that aren't in generated files + * @kind treemap + * @id java/not-generated-file-metric-filter + */ +import java +import external.MetricFilter + +from MetricResult res +where not res.getFile() instanceof GeneratedFile +select res, res.getValue() diff --git a/java/ql/src/filters/RecentDefects.ql b/java/ql/src/filters/RecentDefects.ql new file mode 100644 index 00000000000..c1d22b6da73 --- /dev/null +++ b/java/ql/src/filters/RecentDefects.ql @@ -0,0 +1,21 @@ +/** + * @name Filter: only files recently edited + * @description Filter a defect query to only include results in files that have been changed recently, and modify the message. + * @kind problem + * @id java/recently-changed-file-filter + */ +import java +import external.DefectFilter +import external.VCS + +private pragma[noopt] +predicate recent(File file) { + exists(Commit e | file = e.getAnAffectedFile() | + e.isRecent() and not artificialChange(e) + ) and + exists(file.getLocation()) +} + +from DefectResult res +where recent(res.getFile()) +select res, res.getMessage() diff --git a/java/ql/src/filters/RecentDefectsForMetric.ql b/java/ql/src/filters/RecentDefectsForMetric.ql new file mode 100644 index 00000000000..261977405ee --- /dev/null +++ b/java/ql/src/filters/RecentDefectsForMetric.ql @@ -0,0 +1,21 @@ +/** + * @name Metric filter: only files recently edited + * @description Filter a metric query to only include results in files that have been changed recently, and modify the message. + * @kind treemap + * @id java/recently-changed-file-metric-filter + */ +import java +import external.MetricFilter +import external.VCS + +private pragma[noopt] +predicate recent(File file) { + exists(Commit e | file = e.getAnAffectedFile() | + e.isRecent() and not artificialChange(e) + ) and + exists(file.getLocation()) +} + +from MetricResult res +where recent(res.getFile()) +select res, res.getValue() diff --git a/java/ql/src/filters/SuppressionComment.ql b/java/ql/src/filters/SuppressionComment.ql new file mode 100644 index 00000000000..fdb984fab83 --- /dev/null +++ b/java/ql/src/filters/SuppressionComment.ql @@ -0,0 +1,52 @@ +/** + * @name Filter: Suppression comments + * @description Recognise comments containing `NOSEMMLE` as suppression comments + * when they appear on a line containing an alert or the + * immediately preceding line. As further customisations, + * `NOSEMMLE(some text)` will only suppress alerts where the + * message contains "some text", and `NOSEMMLE/some regex/` will + * only suppress alerts where the message contains a match of the + * regex. No special way of escaping `)` or `/` in the suppression + * comment argument is provided. + * @kind problem + * @id java/nosemmle-suppression-comment-filter + */ +import java +import external.DefectFilter + +class SuppressionComment extends Javadoc { + SuppressionComment() { + this.getAChild*().getText().matches("%NOSEMMLE%") + } + + private string getASuppressionDirective() { + result = this.getAChild*().getText() + .regexpFind("\\bNOSEMMLE\\b(\\([^)]+?\\)|/[^/]+?/|)", _, 0) + } + + private string getAnActualSubstringArg() { + result = this.getASuppressionDirective().regexpCapture("NOSEMMLE\\((.*)\\)", 1) + } + + private string getAnActualRegexArg() { + result = ".*" + this.getASuppressionDirective().regexpCapture("NOSEMMLE/(.*)/", 1) + ".*" + } + + private string getASuppressionRegex() { + result = getAnActualRegexArg() or + exists(string substring | substring = getAnActualSubstringArg() | + result = "\\Q" + substring.replaceAll("\\E", "\\E\\\\E\\Q") + "\\E" + ) or + (result = ".*" and getASuppressionDirective() = "NOSEMMLE") + } + + predicate suppresses(DefectResult res) { + this.getFile() = res.getFile() and + res.getEndLine() - this.getLocation().getEndLine() in [0..2] and + res.getMessage().regexpMatch(this.getASuppressionRegex()) + } +} + +from DefectResult res +where not exists(SuppressionComment s | s.suppresses(res)) +select res, res.getMessage() diff --git a/java/ql/src/java.qll b/java/ql/src/java.qll new file mode 100644 index 00000000000..c533b0af7ec --- /dev/null +++ b/java/ql/src/java.qll @@ -0,0 +1,40 @@ +/** Provides all default Java QL imports. */ + +import semmle.code.FileSystem +import semmle.code.Location + +import semmle.code.java.Annotation +import semmle.code.java.CompilationUnit +import semmle.code.java.ControlFlowGraph +import semmle.code.java.Dependency +import semmle.code.java.Element +import semmle.code.java.Exception +import semmle.code.java.Expr +import semmle.code.java.GeneratedFiles +import semmle.code.java.Generics +import semmle.code.java.Import +import semmle.code.java.J2EE +import semmle.code.java.Javadoc +import semmle.code.java.JDK +import semmle.code.java.JDKAnnotations +import semmle.code.java.JMX +import semmle.code.java.Member +import semmle.code.java.Modifier +import semmle.code.java.Modules +import semmle.code.java.Package +import semmle.code.java.Statement +import semmle.code.java.Type +import semmle.code.java.UnitTests +import semmle.code.java.Variable + +import semmle.code.java.controlflow.BasicBlocks + +import semmle.code.java.metrics.MetricCallable +import semmle.code.java.metrics.MetricElement +import semmle.code.java.metrics.MetricField +import semmle.code.java.metrics.MetricPackage +import semmle.code.java.metrics.MetricRefType +import semmle.code.java.metrics.MetricStmt + +import semmle.code.xml.Ant +import semmle.code.xml.XML diff --git a/java/ql/src/meta/ssa/AmbiguousToString.ql b/java/ql/src/meta/ssa/AmbiguousToString.ql new file mode 100644 index 00000000000..d1a5d1753ab --- /dev/null +++ b/java/ql/src/meta/ssa/AmbiguousToString.ql @@ -0,0 +1,31 @@ +/** + * @name An SSA variable without a unique 'toString()' + * @description An ambiguous 'toString()' indicates overlap in the defining + * sub-classes of 'SsaVariable'. + * @kind problem + * @problem.severity error + * @id java/sanity/non-unique-ssa-tostring + * @tags sanity + */ + +import java +import semmle.code.java.dataflow.SSA + +predicate noToString(SsaVariable v) { + not exists(v.toString()) +} + +predicate multipleToString(SsaVariable v) { + 1 < count(v.toString()) +} + +from SsaVariable ssa, ControlFlowNode n, Variable v, string problem +where + ( + noToString(ssa) and problem = "SSA variable without 'toString()' for " or + multipleToString(ssa) and problem = "SSA variable with multiple 'toString()' results for " + ) and + n = ssa.getCFGNode() and + v = ssa.getSourceVariable().getVariable() +select + n, problem + v diff --git a/java/ql/src/meta/ssa/TooFewPhiInputs.ql b/java/ql/src/meta/ssa/TooFewPhiInputs.ql new file mode 100644 index 00000000000..16216b92a3d --- /dev/null +++ b/java/ql/src/meta/ssa/TooFewPhiInputs.ql @@ -0,0 +1,18 @@ +/** + * @name A phi node without two or more inputs + * @description A phi node should have at least two inputs. + * @kind problem + * @problem.severity error + * @id java/sanity/too-few-phi-inputs + * @tags sanity + */ + +import java +import semmle.code.java.dataflow.SSA + +from SsaPhiNode phi, int inputs +where + inputs = count(SsaVariable v | v = phi.getAPhiInput()) and + inputs < 2 +select + phi, "Phi node for " + phi.getSourceVariable() + " has only " + inputs + " inputs." diff --git a/java/ql/src/meta/ssa/UncertainDefWithoutPrior.ql b/java/ql/src/meta/ssa/UncertainDefWithoutPrior.ql new file mode 100644 index 00000000000..06f32120296 --- /dev/null +++ b/java/ql/src/meta/ssa/UncertainDefWithoutPrior.ql @@ -0,0 +1,25 @@ +/** + * @name An uncertain SSA update without a prior definition + * @description An uncertain SSA update may retain its previous value + * and should therefore have a prior definition. + * @kind problem + * @problem.severity error + * @id java/sanity/uncertain-ssa-update-without-prior-def + * @tags sanity + */ + +import java +import semmle.code.java.dataflow.SSA + +predicate live(SsaVariable v) { + exists(v.getAUse()) or + exists(SsaPhiNode phi | live(phi) and phi.getAPhiInput() = v) or + exists(SsaUncertainImplicitUpdate upd | live(upd) and upd.getPriorDef() = v) +} + +from SsaUncertainImplicitUpdate upd +where + live(upd) and + not exists(upd.getPriorDef()) +select + upd, "No prior definition of " + upd diff --git a/java/ql/src/meta/ssa/UseWithoutUniqueSsaVariable.ql b/java/ql/src/meta/ssa/UseWithoutUniqueSsaVariable.ql new file mode 100644 index 00000000000..8ec45904cd0 --- /dev/null +++ b/java/ql/src/meta/ssa/UseWithoutUniqueSsaVariable.ql @@ -0,0 +1,47 @@ +/** + * @name A variable use without a unique SSA variable + * @description Every variable use that is sufficiently trackable + * should have a unique associated SSA variable. + * @kind problem + * @problem.severity error + * @id java/sanity/use-without-unique-ssa-variable + * @tags sanity + */ + +import java +import semmle.code.java.dataflow.SSA + +class SsaConvertibleReadAccess extends RValue { + SsaConvertibleReadAccess() { + this.getEnclosingCallable().getBody().getBasicBlock().getABBSuccessor*() = this.getBasicBlock() and + ( + not exists(this.getQualifier()) or + this.getVariable() instanceof LocalScopeVariable or + this.getVariable().(Field).isStatic() or + exists(Expr q | q = this.getQualifier() | + q instanceof ThisAccess or + q instanceof SuperAccess or + q instanceof SsaConvertibleReadAccess + ) + ) + } +} + +predicate accessWithoutSourceVariable(SsaConvertibleReadAccess va) { + not exists(SsaSourceVariable v | v.getAnAccess() = va) +} + +predicate readAccessWithoutSsaVariable(SsaConvertibleReadAccess va) { + not exists(SsaVariable v | v.getAUse() = va) +} + +predicate readAccessWithAmbiguousSsaVariable(SsaConvertibleReadAccess va) { + 1 < strictcount(SsaVariable v | v.getAUse() = va) +} + +from SsaConvertibleReadAccess va, string problem +where + accessWithoutSourceVariable(va) and problem = "No source variable" or + readAccessWithoutSsaVariable(va) and problem = "No SSA variable" or + readAccessWithAmbiguousSsaVariable(va) and problem = "Multiple SSA variables" +select va, problem diff --git a/java/ql/src/plugin.xml b/java/ql/src/plugin.xml new file mode 100644 index 00000000000..ff451072779 --- /dev/null +++ b/java/ql/src/plugin.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/java/ql/src/queries.xml b/java/ql/src/queries.xml new file mode 100644 index 00000000000..0d33187fe86 --- /dev/null +++ b/java/ql/src/queries.xml @@ -0,0 +1 @@ + diff --git a/java/ql/src/semmle/code/FileSystem.qll b/java/ql/src/semmle/code/FileSystem.qll new file mode 100755 index 00000000000..5a0162b9f46 --- /dev/null +++ b/java/ql/src/semmle/code/FileSystem.qll @@ -0,0 +1,279 @@ +/** Provides classes for working with files and folders. */ + +import Location + +/** A file or folder. */ +class Container extends @container, Top { + /** + * Gets the absolute, canonical path of this container, using forward slashes + * as path separator. + * + * The path starts with a _root prefix_ followed by zero or more _path + * segments_ separated by forward slashes. + * + * The root prefix is of one of the following forms: + * + * 1. A single forward slash `/` (Unix-style) + * 2. An upper-case drive letter followed by a colon and a forward slash, + * such as `C:/` (Windows-style) + * 3. Two forward slashes, a computer name, and then another forward slash, + * such as `//FileServer/` (UNC-style) + * + * Path segments are never empty (that is, absolute paths never contain two + * contiguous slashes, except as part of a UNC-style root prefix). Also, path + * segments never contain forward slashes, and no path segment is of the + * form `.` (one dot) or `..` (two dots). + * + * Note that an absolute path never ends with a forward slash, except if it is + * a bare root prefix, that is, the path has no path segments. A container + * whose absolute path has no segments is always a `Folder`, not a `File`. + */ + abstract string getAbsolutePath(); + + /** + * Gets a URL representing the location of this container. + * + * For more information see https://lgtm.com/help/ql/locations#providing-urls. + */ + abstract string getURL(); + + /** + * Gets the relative path of this file or folder from the root folder of the + * analyzed source location. The relative path of the root folder itself is + * the empty string. + * + * This has no result if the container is outside the source root, that is, + * if the root folder is not a reflexive, transitive parent of this container. + */ + string getRelativePath() { + exists(string absPath, string pref | + absPath = getAbsolutePath() and sourceLocationPrefix(pref) + | + absPath = pref and result = "" + or + absPath = pref.regexpReplaceAll("/$", "") + "/" + result and + not result.matches("/%") + ) + } + + /** + * Gets the base name of this container including extension, that is, the last + * segment of its absolute path, or the empty string if it has no segments. + * + * Here are some examples of absolute paths and the corresponding base names + * (surrounded with quotes to avoid ambiguity): + * + * + * + * + * + * + * + * + * + *
    Absolute pathBase name
    "/tmp/tst.java""tst.java"
    "C:/Program Files (x86)""Program Files (x86)"
    "/"""
    "C:/"""
    "D:/"""
    "//FileServer/"""
    + */ + string getBaseName() { + result = getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1) + } + + /** + * Gets the extension of this container, that is, the suffix of its base name + * after the last dot character, if any. + * + * In particular, + * + * - if the name does not include a dot, there is no extension, so this + * predicate has no result; + * - if the name ends in a dot, the extension is the empty string; + * - if the name contains multiple dots, the extension follows the last dot. + * + * Here are some examples of absolute paths and the corresponding extensions + * (surrounded with quotes to avoid ambiguity): + * + * + * + * + * + * + * + * + *
    Absolute pathExtension
    "/tmp/tst.java""java"
    "/tmp/.classpath""classpath"
    "/bin/bash"not defined
    "/tmp/tst2."""
    "/tmp/x.tar.gz""gz"
    + */ + string getExtension() { + result = getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3) + } + + /** + * Gets the stem of this container, that is, the prefix of its base name up to + * (but not including) the last dot character if there is one, or the entire + * base name if there is not. + * + * Here are some examples of absolute paths and the corresponding stems + * (surrounded with quotes to avoid ambiguity): + * + * + * + * + * + * + * + * + *
    Absolute pathStem
    "/tmp/tst.java""tst"
    "/tmp/.classpath"""
    "/bin/bash""bash"
    "/tmp/tst2.""tst2"
    "/tmp/x.tar.gz""x.tar"
    + */ + string getStem() { + result = getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1) + } + + /** Gets the parent container of this file or folder, if any. */ + Container getParentContainer() { + containerparent(result, this) + } + + /** Gets a file or sub-folder in this container. */ + Container getAChildContainer() { + this = result.getParentContainer() + } + + /** Gets a file in this container. */ + File getAFile() { + result = getAChildContainer() + } + + /** Gets the file in this container that has the given `baseName`, if any. */ + File getFile(string baseName) { + result = getAFile() and + result.getBaseName() = baseName + } + + /** Gets a sub-folder in this container. */ + Folder getAFolder() { + result = getAChildContainer() + } + + /** Gets the sub-folder in this container that has the given `baseName`, if any. */ + Folder getFolder(string baseName) { + result = getAFolder() and + result.getBaseName() = baseName + } + + /** + * Gets a textual representation of the path of this container. + * + * This is the absolute path of the container. + */ + override string toString() { + result = getAbsolutePath() + } + + /** + * DEPRECATED: use `getAbsolutePath()`, `getBaseName()` or `getStem()` instead. + * + * Gets the name of this container. + */ + deprecated + string getName() { + result = getAbsolutePath() + } + + /** + * DEPRECATED: use `getBaseName()` or `getStem()` instead. + * + * The short name of this container, excluding its path and (for files) extension. + * + * For folders, the short name includes the extension (if any), so the short name + * of the folder with absolute path `/home/user/.m2` is `.m2`. + */ + deprecated + string getShortName() { + folders(this,_,result) or + files(this,_,result,_,_) + } + + /** + * DEPRECATED: use `getAbsolutePath()` instead. + * + * Gets the full name of this container, including its path and extension (if any). + */ + deprecated + string getFullName() { + result = getAbsolutePath() + } +} + +/** A folder. */ +class Folder extends Container, @folder { + override string getAbsolutePath() { + folders(this, result, _) + } + + /** Gets the URL of this folder. */ + override string getURL() { + result = "folder://" + getAbsolutePath() + } +} + +/** + * A file. + * + * Note that `File` extends `Container` as it may be a `jar` file. + */ +class File extends Container, @file { + override string getAbsolutePath() { + files(this, result, _, _, _) + } + + /** Gets the URL of this file. */ + override string getURL() { + result = "file://" + this.getAbsolutePath() + ":0:0:0:0" + } + + /** + * DEPRECATED: use `getAbsolutePath()`, `getBaseName()` or `getStem()` instead. + * + * Holds if this file has the specified `name`. + */ + deprecated + predicate hasName(string name) { name = this.getAbsolutePath() } +} + +/** + * A Java archive file with a ".jar" extension. + */ +class JarFile extends File { + JarFile() { + getExtension() = "jar" + } + + /** + * Gets the main attribute with the specified `key` + * from this JAR file's manifest. + */ + string getManifestMainAttribute(string key) { + jarManifestMain(this, key, result) + } + + /** + * Gets the "Specification-Version" main attribute + * from this JAR file's manifest. + */ + string getSpecificationVersion() { + result = getManifestMainAttribute("Specification-Version") + } + + /** + * Gets the "Implementation-Version" main attribute + * from this JAR file's manifest. + */ + string getImplementationVersion() { + result = getManifestMainAttribute("Implementation-Version") + } + + /** + * Gets the per-entry attribute for the specified `entry` and `key` + * from this JAR file's manifest. + */ + string getManifestEntryAttribute(string entry, string key) { + jarManifestEntries(this, entry, key, result) + } +} diff --git a/java/ql/src/semmle/code/Location.qll b/java/ql/src/semmle/code/Location.qll new file mode 100755 index 00000000000..18e99fddb60 --- /dev/null +++ b/java/ql/src/semmle/code/Location.qll @@ -0,0 +1,158 @@ +/** + * Provides classes and predicates for working with locations. + * + * Locations represent parts of files and are used to map elements to their source location. + */ + +import FileSystem +import semmle.code.java.Element + +/** Holds if element `e` has name `name`. */ +predicate hasName(Element e, string name) { + classes(e,name,_,_) or + interfaces(e,name,_,_) or + primitives(e,name) or + constrs(e,name,_,_,_,_) or + methods(e,name,_,_,_,_) or + fields(e,name,_,_,_) or + packages(e,name) or + files(e,_,name,_,_) or + paramName(e,name) or + exists(int pos | + params(e,_,pos,_,_) and + not paramName(e,_) and + name = "p"+pos + ) or + localvars(e,name,_,_) or + typeVars(e,name,_,_,_) or + wildcards(e,name,_) or + arrays(e,name,_,_,_) or + modifiers(e,name) +} + +/** + * Top is the root of the QL type hierarchy; it defines some default + * methods for obtaining locations and a standard `toString()` method. + */ +class Top extends @top { + /** Gets the source location for this element. */ + Location getLocation() { fixedHasLocation(this, result, _) } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [LGTM locations](https://lgtm.com/help/ql/locations). + */ + predicate hasLocationInfo(string filepath, int startline, int startcolumn, int endline, int endcolumn) { + exists(File f, Location l | fixedHasLocation(this, l, f) | + locations_default(l,f,startline,startcolumn,endline,endcolumn) and + filepath = f.getAbsolutePath() + ) + } + + /** Gets the file associated with this element. */ + File getFile() { + fixedHasLocation(this, _, result) + } + + /** + * Gets the total number of lines that this element ranges over, + * including lines of code, comment and whitespace-only lines. + */ + int getTotalNumberOfLines() { + numlines(this, result, _, _) + } + + /** Gets the number of lines of code that this element ranges over. */ + int getNumberOfLinesOfCode() { + numlines(this, _, result, _) + } + + /** Gets the number of comment lines that this element ranges over. */ + int getNumberOfCommentLines() { + numlines(this, _, _, result) + } + + /** Gets a textual representation of this element. */ + string toString() { hasName(this, result) } +} + +/** A location maps language elements to positions in source files. */ +class Location extends @location { + /** Gets the line number where this location starts. */ + int getStartLine() { locations_default(this,_,result,_,_,_) } + + /** Gets the column number where this location starts. */ + int getStartColumn() { locations_default(this,_,_,result,_,_) } + + /** Gets the line number where this location ends. */ + int getEndLine() { locations_default(this,_,_,_,result,_) } + + /** Gets the column number where this location ends. */ + int getEndColumn() { locations_default(this,_,_,_,_,result) } + + /** + * Gets the total number of lines that this location ranges over, + * including lines of code, comment and whitespace-only lines. + */ + int getNumberOfLines() { + exists(@sourceline s | hasLocation(s, this) | + numlines(s,result,_,_) or + (not numlines(s,_,_,_) and result = 0) + ) + } + + /** Gets the number of lines of code that this location ranges over. */ + int getNumberOfLinesOfCode() { + exists(@sourceline s | hasLocation(s, this) | + numlines(s,_,result,_) or + (not numlines(s,_,_,_) and result = 0) + ) + } + + /** Gets the number of comment lines that this location ranges over. */ + int getNumberOfCommentLines() { + exists(@sourceline s | hasLocation(s, this) | + numlines(s,_,_,result) or + (not numlines(s,_,_,_) and result = 0) + ) + } + + /** Gets the file containing this location. */ + File getFile() { locations_default(this,result,_,_,_,_) } + + /** Gets a string representation containing the file and range for this location. */ + string toString() { + exists(File f, int startLine, int endLine | locations_default(this,f,startLine,_,endLine,_) | + if endLine = startLine then + result = f.toString() + ":" + startLine.toString() + else + result = f.toString() + ":" + startLine.toString() + "-" + endLine.toString() + ) + } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [LGTM locations](https://lgtm.com/help/ql/locations). + */ + predicate hasLocationInfo(string filepath, int startline, int startcolumn, int endline, int endcolumn) { + exists(File f | locations_default(this,f,startline,startcolumn,endline,endcolumn) | + filepath = f.getAbsolutePath() + ) + } +} + +private predicate hasSourceLocation(Top l, Location loc, File f) { + hasLocation(l, loc) and f = loc.getFile() and f.getExtension() = "java" +} + +cached +private predicate fixedHasLocation(Top l, Location loc, File f) { + hasSourceLocation(l, loc, f) or + (hasLocation(l, loc) and not hasSourceLocation(l, _, _) and locations_default(loc, f, _, _, _, _)) +} diff --git a/java/ql/src/semmle/code/java/Annotation.qll b/java/ql/src/semmle/code/java/Annotation.qll new file mode 100755 index 00000000000..377e57bcc92 --- /dev/null +++ b/java/ql/src/semmle/code/java/Annotation.qll @@ -0,0 +1,158 @@ +/** + * Provides classes and predicates for working with Java annotations. + * + * Annotations are used to add meta-information to language elements in a + * uniform fashion. They can be seen as typed modifiers that can take + * parameters. + * + * Each annotation type has zero or more annotation elements that contain a + * name and possibly a value. + */ + +import Element +import Expr +import Type +import Member +import JDKAnnotations + +/** Any annotation used to annotate language elements with meta-information. */ +class Annotation extends @annotation, Expr { + /** Holds if this annotation applies to a declaration. */ + predicate isDeclAnnotation() { this instanceof DeclAnnotation } + + /** Holds if this annotation applies to a type. */ + predicate isTypeAnnotation() { this instanceof TypeAnnotation } + + /** Gets the element being annotated. */ + Element getAnnotatedElement() { this.getParent() = result } + + /** Gets the annotation type declaration for this annotation. */ + override AnnotationType getType() { result = Expr.super.getType() } + + /** Gets the annotation element with the specified `name`. */ + AnnotationElement getAnnotationElement(string name) { + result = this.getType().getAnnotationElement(name) + } + + /** Gets a value of an annotation element. */ + Expr getAValue() { filteredAnnotValue(this, _, result) } + + /** Gets the value of the annotation element with the specified `name`. */ + Expr getValue(string name) { + filteredAnnotValue(this, this.getAnnotationElement(name), result) + } + + /** Gets the element being annotated. */ + Element getTarget() { + exprs(this, _, _, result, _) + } + + override string toString() { result = this.getType().getName() } + + /** This expression's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "Annotation" } + + /** + * Gets a value of the annotation element with the specified `name`, which must be declared as an array + * type. + * + * If the annotation element is defined with an array initializer, then the returned value will + * be one of the elements of that array. Otherwise, the returned value will be the single + * expression defined for the value. + */ + Expr getAValue(string name) { + getType().getAnnotationElement(name).getType() instanceof Array and + exists(Expr value | value = getValue(name) | + if value instanceof ArrayInit then + result = value.(ArrayInit).getAnInit() + else + result = value + ) + } +} + +/** An `Annotation` that applies to a declaration. */ +class DeclAnnotation extends @declannotation, Annotation { +} + +/** An `Annotation` that applies to a type. */ +class TypeAnnotation extends @typeannotation, Annotation { +} + +/** + * There may be duplicate entries in annotValue(...) - one entry for + * information populated from bytecode, and one for information populated + * from source. This removes the duplication. + */ +private +predicate filteredAnnotValue(Annotation a, Method m, Expr val) { + annotValue(a, m, val) and + (sourceAnnotValue(a, m, val) or not sourceAnnotValue(a, m, _)) +} + +private +predicate sourceAnnotValue(Annotation a, Method m, Expr val) { + annotValue(a, m, val) and + val.getFile().getExtension() = "java" +} + +/** An abstract representation of language elements that can be annotated. */ +class Annotatable extends Element { + /** Holds if this element has an annotation. */ + predicate hasAnnotation() { exists(Annotation a | a.getAnnotatedElement() = this) } + + /** Holds if this element has the specified annotation. */ + predicate hasAnnotation(string package, string name) { + exists(AnnotationType at | at = getAnAnnotation().getType() | at.nestedName() = name and at.getPackage().getName() = package) + } + + /** Gets an annotation that applies to this element. */ + Annotation getAnAnnotation() { result.getAnnotatedElement() = this } + + /** + * Holds if this or any enclosing `Annotatable` has a `@SuppressWarnings("")` + * annotation attached to it for the specified `category`. + */ + predicate suppressesWarningsAbout(string category) { + exists(string withQuotes + | withQuotes = ((SuppressWarningsAnnotation) getAnAnnotation()).getASuppressedWarning() + | category = withQuotes.substring(1, withQuotes.length() - 1) + ) or + this.(Member).getDeclaringType().suppressesWarningsAbout(category) or + this.(Expr).getEnclosingCallable().suppressesWarningsAbout(category) or + this.(Stmt).getEnclosingCallable().suppressesWarningsAbout(category) or + this.(NestedClass).getEnclosingType().suppressesWarningsAbout(category) or + this.(LocalVariableDecl).getCallable().suppressesWarningsAbout(category) + } +} + +/** An annotation type is a special kind of interface type declaration. */ +class AnnotationType extends Interface { + AnnotationType() { isAnnotType(this) } + + /** Gets the annotation element with the specified `name`. */ + AnnotationElement getAnnotationElement(string name) { + methods(result,_,_,_,this,_) and result.hasName(name) + } + + /** Gets an annotation element that is a member of this annotation type. */ + AnnotationElement getAnAnnotationElement() { + methods(result,_,_,_,this,_) + } + + /** Holds if this annotation type is annotated with the meta-annotation `@Inherited`. */ + predicate isInherited() { + exists(Annotation ann | + ann.getAnnotatedElement() = this and + ann.getType().hasQualifiedName("java.lang.annotation", "Inherited") + ) + } +} + +/** An annotation element is a member declared in an annotation type. */ +class AnnotationElement extends Member { + AnnotationElement() { isAnnotElem(this) } + + /** Gets the type of this annotation element. */ + Type getType() { methods(this,_,_,result,_,_) } +} diff --git a/java/ql/src/semmle/code/java/Collections.qll b/java/ql/src/semmle/code/java/Collections.qll new file mode 100644 index 00000000000..7e64456bc4d --- /dev/null +++ b/java/ql/src/semmle/code/java/Collections.qll @@ -0,0 +1,130 @@ +import java + +/** + * The type `t` is a parameterization of `g`, where the `i`-th type parameter of + * `g` is instantiated to `a`? + * + * For example, `List` parameterizes `List`, instantiating its `0`-th + * type parameter to `Integer`, while the raw type `List` also parameterizes + * `List`, instantiating the type parameter to `Object`. + */ +predicate instantiates(RefType t, GenericType g, int i, RefType arg) { + t = g.getAParameterizedType() and exists(g.getTypeParameter(i)) and + ( + arg = t.(ParameterizedType).getTypeArgument(i) or + t instanceof RawType and arg instanceof TypeObject + ) +} + +/** + * Generalisation of `instantiates` that takes subtyping into account: + * + * - `HashSet` indirectly instantiates `Collection` (but also `HashSet` and `Set`), + * with the `0`-th type parameter being `Integer`; + * - a class `MyList extends ArrayList` also instantiates `Collection` + * (as well as `AbstractList`, `AbstractCollection` and `List`), with the `0`-th type + * parameter being `Runnable`; + * - the same is true of `class MyOtherList extends ArrayList` (note that + * it does _not_ instantiate the type parameter to `T`); + * - a class `MyIntMap extends HashMap` instantiates `Map` (among others) + * with the `0`-th type parameter being `Integer` and the `1`-th type parameter being `V`. + */ +predicate indirectlyInstantiates(RefType t, GenericType g, int i, RefType arg) { + exists(RefType tsrc | tsrc = t.getSourceDeclaration() | + // base case: `t` directly instantiates `g` + tsrc = g and instantiates(t, g, i, arg) + or + // inductive step + exists(RefType sup, RefType suparg | + // follow `extends`/`implements` + (extendsReftype(tsrc, sup) or implInterface(tsrc, sup)) and + // check whether the subtype instantiates `g` + indirectlyInstantiates(sup, g, i, suparg) + | + // if `t` is itself an instantiation of `tsrc` and `sup` instantiates + // `g` to one of the type parameters of `tsrc`, we return the corresponding + // instantiation in `t` + exists(int j | suparg = tsrc.(GenericType).getTypeParameter(j) | + instantiates(t, tsrc, j, arg) + ) + or + // otherwise, we directly return `suparg` + not ( + t = tsrc.(GenericType).getAParameterizedType() and + suparg = tsrc.(GenericType).getATypeParameter() + ) and + arg = suparg + ) + ) +} + +/** A reference type that extends a parameterization of `java.util.Collection`. */ +class CollectionType extends RefType { + CollectionType() { + exists(ParameterizedInterface coll | + coll.getSourceDeclaration().hasQualifiedName("java.util", "Collection") + | + this.hasSupertype*(coll) + ) + } + + /** Gets the type of elements stored in this collection. */ + RefType getElementType() { + exists(GenericInterface coll | coll.hasQualifiedName("java.util", "Collection") | + indirectlyInstantiates(this, coll, 0, result) + ) + } +} + +/** A method declared in a collection type. */ +class CollectionMethod extends Method { + CollectionMethod() { + this.getDeclaringType() instanceof CollectionType + } + + /** Gets the type of elements of the collection to which this method belongs. */ + RefType getReceiverElementType() { + result = this.getDeclaringType().(CollectionType).getElementType() + } +} + +/** The `size` method on `java.util.Collection`. */ +class CollectionSizeMethod extends CollectionMethod { + CollectionSizeMethod() { + this.hasName("size") and this.hasNoParameters() + } +} + +/** A method that mutates the collection it belongs to. */ +class CollectionMutator extends CollectionMethod { + CollectionMutator() { + this.getName().regexpMatch("add.*|remove.*|push|pop|clear") + } +} + +/** A method call that mutates a collection. */ +class CollectionMutation extends MethodAccess { + CollectionMutation() { + this.getMethod() instanceof CollectionMutator + } + + predicate resultIsChecked() { + not this.getParent() instanceof ExprStmt + } +} + +/** A method that queries the contents of a collection without mutating it. */ +class CollectionQueryMethod extends CollectionMethod { + CollectionQueryMethod() { + this.getName().regexpMatch("contains|containsAll|get|size|peek") + } +} + +/** A `new` expression that allocates a fresh, empty collection. */ +class FreshCollection extends ClassInstanceExpr { + FreshCollection() { + this.getConstructedType() instanceof CollectionType and + this.getNumArgument() = 0 and + not exists(this.getAnonymousClass()) + } +} diff --git a/java/ql/src/semmle/code/java/CompilationUnit.qll b/java/ql/src/semmle/code/java/CompilationUnit.qll new file mode 100755 index 00000000000..c2661a498db --- /dev/null +++ b/java/ql/src/semmle/code/java/CompilationUnit.qll @@ -0,0 +1,41 @@ +/** + * Provides classes and predicates for working with Java compilation units. + */ + +import Element +import Package +import semmle.code.FileSystem + +/** + * A compilation unit is a `.java` or `.class` file. + */ +class CompilationUnit extends Element, File { + CompilationUnit() { + cupackage(this,_) + } + + /** Gets the name of the compilation unit (not including its extension). */ + override string getName() { + result = Element.super.getName() + } + + /** + * Holds if this compilation unit has the specified `name`, + * which must not include the file extension. + */ + override predicate hasName(string name) { + Element.super.hasName(name) + } + + override string toString() { + result = Element.super.toString() + } + + /** Gets the declared package of this compilation unit. */ + Package getPackage() { cupackage(this,result) } + + /** + * Gets the module associated with this compilation unit, if any. + */ + Module getModule() { cumodule(this, result) } +} diff --git a/java/ql/src/semmle/code/java/Completion.qll b/java/ql/src/semmle/code/java/Completion.qll new file mode 100644 index 00000000000..bd32ca4af5b --- /dev/null +++ b/java/ql/src/semmle/code/java/Completion.qll @@ -0,0 +1,87 @@ +/** + * Provides classes and predicates for representing completions. + */ + +/* + * A completion represents how a statement or expression terminates. + * + * There are five kinds of completions: normal completion, + * `return` completion, `break` completion, + * `continue` completion, and `throw` completion. + * + * Normal completions are further subdivided into boolean completions and all + * other normal completions. A boolean completion adds the information that the + * cfg node terminated with the given boolean value due to a subexpression + * terminating with the other given boolean value. This is only + * relevant for conditional contexts in which the value controls the + * control-flow successor. + */ + +import java + +/** + * A label of a `LabeledStmt`. + */ +newtype Label = MkLabel(string l) { exists(LabeledStmt lbl | l = lbl.getLabel()) } + + +/** + * Either a `Label` or nothing. + */ +newtype MaybeLabel = JustLabel(Label l) or NoLabel() + + +/** + * A completion of a statement or an expression. + */ +newtype Completion = + /** + * The statement or expression completes normally and continues to the next statement. + */ + NormalCompletion() or + /** + * The statement or expression completes by returning from the function. + */ + ReturnCompletion() or + /** + * The expression completes with value `outerValue` overall and with the last control + * flow node having value `innerValue`. + */ + BooleanCompletion(boolean outerValue, boolean innerValue) { + (outerValue = true or outerValue = false) and (innerValue = true or innerValue = false) + } or + /** + * The expression or statement completes via a `break` statement. + */ + BreakCompletion(MaybeLabel l) or + /** + * The expression or statement completes via a `continue` statement. + */ + ContinueCompletion(MaybeLabel l) or + /** + * The expression or statement completes by throwing a `ThrowableType`. + */ + ThrowCompletion(ThrowableType tt) + + +ContinueCompletion anonymousContinueCompletion() { + result = ContinueCompletion(NoLabel()) +} + +ContinueCompletion labelledContinueCompletion(Label l) { + result = ContinueCompletion(JustLabel(l)) +} + +BreakCompletion anonymousBreakCompletion() { + result = BreakCompletion(NoLabel()) +} + +BreakCompletion labelledBreakCompletion(Label l) { + result = BreakCompletion(JustLabel(l)) +} + +/** Gets the completion `booleanCompletion(value, value)`. */ +Completion basicBooleanCompletion(boolean value) { + result = BooleanCompletion(value, value) +} + diff --git a/java/ql/src/semmle/code/java/Concurrency.qll b/java/ql/src/semmle/code/java/Concurrency.qll new file mode 100644 index 00000000000..d6f4660a39f --- /dev/null +++ b/java/ql/src/semmle/code/java/Concurrency.qll @@ -0,0 +1,48 @@ + +import java + +/** + * Holds if `e` is synchronized by a local synchronized statement `sync` on the variable `v`. + */ +predicate locallySynchronizedOn(Expr e, SynchronizedStmt sync, Variable v) { + e.getEnclosingStmt().getParent+() = sync and + sync.getExpr().(VarAccess).getVariable() = v +} + +/** + * Holds if `e` is synchronized by a local synchronized statement on a `this` of type `thisType`, or by a synchronized + * modifier on the enclosing (non-static) method. + */ +predicate locallySynchronizedOnThis(Expr e, RefType thisType) { + exists(SynchronizedStmt sync | e.getEnclosingStmt().getParent+() = sync | + sync.getExpr().getProperExpr().(ThisAccess).getType().(RefType).getSourceDeclaration() = thisType + ) + or + exists(SynchronizedCallable c | c = e.getEnclosingCallable() | + not c.isStatic() and thisType = c.getDeclaringType() + ) +} + +/** + * Holds if `e` is synchronized by a `synchronized` modifier on the enclosing (static) method. + */ +predicate locallySynchronizedOnClass(Expr e, RefType classType) { + exists(SynchronizedCallable c | c = e.getEnclosingCallable() | + c.isStatic() and classType = c.getDeclaringType() + ) +} + +/** + * A callable that is synchronized on its enclosing instance, either by a `synchronized` modifier, or + * by having a body which is precisely `synchronized(this) { ... }`. + */ +class SynchronizedCallable extends Callable { + SynchronizedCallable() { + this.isSynchronized() + or + // The body is just `synchronized(this) { ... }`. + exists(SynchronizedStmt s | this.getBody().(SingletonBlock).getStmt() = s | + s.getExpr().(ThisAccess).getType() = this.getDeclaringType() + ) + } +} diff --git a/java/ql/src/semmle/code/java/ControlFlowGraph.qll b/java/ql/src/semmle/code/java/ControlFlowGraph.qll new file mode 100644 index 00000000000..94a4f633e5a --- /dev/null +++ b/java/ql/src/semmle/code/java/ControlFlowGraph.qll @@ -0,0 +1,1009 @@ +/** + * Provides classes and predicates for computing expression-level intra-procedural control flow graphs. + * + * The only API exported by this library are the toplevel classes `ControlFlowNode` + * and its subclass `ConditionNode`, which wrap the successor relation and the + * concept of true- and false-successors of conditions. A cfg node may either be a + * statement, an expression, or the enclosing callable, indicating that + * execution of the callable terminates. + */ + +/* + * The implementation is centered around the concept of a _completion_, which + * models how the execution of a statement or expression terminates. + * Completions are represented as an algebraic data type `Completion` defined in + * `Completion.qll`. + * + * The CFG is built by structural recursion over the AST. To achieve this the + * CFG edges related to a given AST node, `n`, is divided into three categories: + * 1. The in-going edge that points to the first CFG node to execute when the + * `n` is going to be executed. + * 2. The out-going edges for control-flow leaving `n` that are going to some + * other node in the surrounding context of `n`. + * 3. The edges that have both of their end-points entirely within the AST + * node and its children. + * The edges in (1) and (2) are inherently non-local and are therefore + * initially calculated as half-edges, that is, the single node, `k`, of the + * edge contained within `n`, by the predicates `k = first(n)` and + * `last(n, k, _)`, respectively. The edges in (3) can then be enumerated + * directly by the predicate `succ` by calling `first` and `last` recursively + * on the children of `n` and connecting the end-points. This yields the entire + * CFG, since all edges are in (3) for _some_ AST node. + * + * The third parameter of `last` is the completion, which is necessary to + * distinguish the out-going edges from `n`. Note that the completion changes + * as the calculation of `last` proceeds outward through the AST; for example, + * a `breakCompletion` is caught up by its surrounding loop and turned into a + * `normalCompletion`, or a `normalCompletion` proceeds outward through the end + * of a `finally` block and is turned into whatever completion was caught by + * the `finally`, or a `booleanCompletion(false, _)` occurs in a loop condition + * and is turned into a `normalCompletion` of the entire loop. When the edge is + * eventually connected we use the completion at that level of the AST as the + * label of the edge, thus creating an edge-labelled CFG. + * + * An important goal of the CFG is to get the order of side-effects correct. + * Most expressions can have side-effects and must therefore be modeled in the + * CFG in AST post-order. For example, a `MethodAccess` evaluates its arguments + * before the call. Most statements don't have side-effects, but merely affect + * the control-flow and some could therefore be excluded from the CFG. However, + * as a design choice, all statements are included in the CFG and generally + * serve as their own entry-points, thus executing in some version of AST + * pre-order. A few notable exceptions are `ReturnStmt`, `ThrowStmt`, + * `SynchronizedStmt`, `ThisConstructorInvocationStmt`, and + * `SuperConstructorInvocationStmt`, which all have side-effects and therefore + * are modeled in side-effect order. Loop statement nodes are only passed on + * entry, after which control goes back and forth between body and loop + * condition. + * + * Some out-going edges from boolean expressions have a known value and in some + * contexts this affects the possible successors. For example, in `if(A || B)` + * a short-circuit edge that skips `B` must be true and can therefore only lead + * to the then-branch. If the `||` is modeled in post-order then this + * information is lost, and consequently it is better to model `||` and `&&` in + * pre-order. The conditional expression `? :` is also modeled in pre-order to + * achieve consistent CFGs for the equivalent `A && B` and `A ? B : false`. + * Finally, the logical negation is also modeled in pre-order to achieve + * consistent CFGs for the equivalent `!(A || B)` and `!A && !B`. The boolean + * value `b` is tracked with the completion `booleanCompletion(b, _)`. + * + * Note that the second parameter in a `booleanCompletion` isn't needed to + * calculate the CFG. It is, however, needed to track the value of the + * sub-expression. For example, this ensures that the false-successor of the + * `ConditionNode` `A` in `if(!(A && B))` can be correctly identified as the + * then-branch (even though this completion turns into a + * `booleanCompletion(true, _)` from the perspective of the `if`-node). + * + * As a final note, expressions that aren't actually executed in the usual + * sense are excluded from the CFG. This covers, for example, parentheses, + * l-values that aren't r-values as well, and expressions in `ConstCase`s. + * For example, the `x` in `x=3` is not in the CFG, but the `x` in `x+=3` is. + */ + +import java + +private import Completion + +/** A node in the expression-level control-flow graph. */ +class ControlFlowNode extends Top, @exprparent { + /** Gets the statement containing this node, if any. */ + Stmt getEnclosingStmt() { + result = this or + result = this.(Expr).getEnclosingStmt() + } + + /** Gets the immediately enclosing callable whose body contains this node. */ + Callable getEnclosingCallable() { + result = this or + result = this.(Stmt).getEnclosingCallable() or + result = this.(Expr).getEnclosingCallable() + } + + /** Gets an immediate successor of this node. */ + ControlFlowNode getASuccessor() { + result = succ(this) + } + + /** Gets an immediate predecessor of this node. */ + ControlFlowNode getAPredecessor() { + this = succ(result) + } + + /** Gets an exception successor of this node. */ + ControlFlowNode getAnExceptionSuccessor() { + result = succ(this, ThrowCompletion(_)) + } + + /** Gets a successor of this node that is neither an exception successor nor a jump (break, continue, return). */ + ControlFlowNode getANormalSuccessor() { + result = succ(this, BooleanCompletion(_, _)) or + result = succ(this, NormalCompletion()) + } + + BasicBlock getBasicBlock() { + result.getANode() = this + } +} + +/** Gets the intra-procedural successor of `n`. */ +private ControlFlowNode succ(ControlFlowNode n) { + result = succ(n, _) +} + +private cached module ControlFlowGraphImpl { + + /** + * Gets a label that applies to this statement. + */ + private Label getLabel(Stmt s) { + exists(LabeledStmt l | s = l.getStmt() | + result = MkLabel(l.getLabel()) or + result = getLabel(l) + ) + } + + /** + * A throwable that's a (reflexive, transitive) supertype of an unchecked + * exception. Besides the unchecked exceptions themselves, this includes + * `java.lang.Throwable` and `java.lang.Exception`. + */ + private class UncheckedThrowableSuperType extends RefType { + UncheckedThrowableSuperType() { + this instanceof TypeThrowable or + this instanceof TypeException or + this instanceof UncheckedThrowableType + } + + /** An unchecked throwable that is a subtype of this `UncheckedThrowableSuperType` and + * sits as high as possible in the type hierarchy. This is mostly unique except for + * `TypeThrowable` which results in both `TypeError` and `TypeRuntimeException`. + */ + UncheckedThrowableType getAnUncheckedSubtype() { + result = (UncheckedThrowableType)this or + result instanceof TypeError and this instanceof TypeThrowable or + result instanceof TypeRuntimeException and (this instanceof TypeThrowable or this instanceof TypeException) + } + } + + /** + * Bind `t` to an exception type that may be thrown during execution of `n`, + * either because `n` is a `throw` statement, or because it is a call + * that may throw an exception, or because it is a cast and a + * `ClassCastException` is expected. + */ + private predicate mayThrow(ControlFlowNode n, ThrowableType t) { + t = n.(ThrowStmt).getThrownExceptionType() or + exists(Call c | c = n | + t = c.getCallee().getAThrownExceptionType() or + uncheckedExceptionFromCatch(n, t) or + uncheckedExceptionFromFinally(n, t) + ) or + exists(CastExpr c | c = n | + t instanceof TypeClassCastException and + uncheckedExceptionFromCatch(n, t) + ) + } + + /** + * Bind `t` to an unchecked exception that may transfer control to a finally + * block inside which `n` is nested. + */ + private predicate uncheckedExceptionFromFinally(ControlFlowNode n, ThrowableType t) { + exists(TryStmt try | + n.getEnclosingStmt().getParent+() = try.getBlock() or + n.(Expr).getParent*() = try.getAResource() + | + exists(try.getFinally()) and + (t instanceof TypeError or t instanceof TypeRuntimeException) + ) + } + + /** + * Bind `t` to all unchecked exceptions that may be caught by some + * `try-catch` inside which `n` is nested. + */ + private predicate uncheckedExceptionFromCatch(ControlFlowNode n, ThrowableType t) { + exists(TryStmt try, UncheckedThrowableSuperType caught | + n.getEnclosingStmt().getParent+() = try.getBlock() or + n.(Expr).getParent*() = try.getAResource() + | + t = caught.getAnUncheckedSubtype() and + try.getACatchClause().getACaughtType() = caught + ) + } + + /** + * Gets an exception type that may be thrown during execution of the + * body or the resources (if any) of `try`. + */ + private ThrowableType thrownInBody(TryStmt try) { + exists(ControlFlowNode n | mayThrow(n, result) | + n.getEnclosingStmt().getParent+() = try.getBlock() or + n.(Expr).getParent*() = try.getAResource() + ) + } + + /** + * Bind `thrown` to an exception type that may be thrown during execution + * of the body or the resource declarations of the `try` block to which + * `c` belongs, such that `c` definitely catches that exception (if no + * prior catch clause handles it). + */ + private predicate mustCatch(CatchClause c, ThrowableType thrown) { + thrown = thrownInBody(c.getTry()) and + hasSubtype*(c.getACaughtType(), thrown) + } + + /** + * Bind `thrown` to an exception type that may be thrown during execution + * of the body or the resource declarations of the `try` block to which + * `c` belongs, such that `c` may _not_ catch that exception. + * + * This predicate computes the complement of `mustCatch` over those + * exception types that are thrown in the body/resource declarations of + * the corresponding `try`. + */ + private predicate mayNotCatch(CatchClause c, ThrowableType thrown) { + thrown = thrownInBody(c.getTry()) and + not hasSubtype*(c.getACaughtType(), thrown) + } + + /** + * Bind `thrown` to an exception type that may be thrown during execution + * of the body or the resource declarations of the `try` block to which + * `c` belongs, such that `c` possibly catches that exception. + */ + private predicate mayCatch(CatchClause c, ThrowableType thrown) { + mustCatch(c, thrown) or + mayNotCatch(c, thrown) and exists(c.getACaughtType().commonSubtype(thrown)) + } + + /** + * Given an exception type `thrown`, determine which catch clauses of + * `try` may possibly catch that exception. + */ + private CatchClause handlingCatchClause(TryStmt try, ThrowableType thrown) { + exists(int i | result = try.getCatchClause(i) | + mayCatch(result, thrown) and + not exists(int j | j < i | mustCatch(try.getCatchClause(j), thrown)) + ) + } + + /** + * Boolean expressions that occur in a context in which their value affect control flow. + * That is, contexts where the control-flow edges depend on `value` given that `b` ends + * with a `booleanCompletion(value, _)`. + */ + private predicate inBooleanContext(Expr b) { + exists(LogicExpr logexpr | + logexpr.(BinaryExpr).getLeftOperand() = b or + // Cannot use LogicExpr.getAnOperand or BinaryExpr.getAnOperand as they remove parentheses. + logexpr.(BinaryExpr).getRightOperand() = b and inBooleanContext(logexpr) or + logexpr.(UnaryExpr).getExpr() = b and inBooleanContext(logexpr) + ) + or + exists(ParExpr parexpr | + parexpr.getExpr() = b and inBooleanContext(parexpr) + ) + or + exists(ConditionalExpr condexpr | + condexpr.getCondition() = b or + (condexpr.getTrueExpr() = b or condexpr.getFalseExpr() = b) and inBooleanContext(condexpr) + ) + or + exists(ConditionalStmt condstmt | + condstmt.getCondition() = b + ) + } + + /** + * A virtual method with a unique implementation. That is, the method does not + * participate in overriding and there are no call targets that could dispatch + * to both this and another method. + */ + private class EffectivelyNonVirtualMethod extends SrcMethod { + EffectivelyNonVirtualMethod() { + exists(this.getBody()) and + this.isVirtual() and + not this = any(Method m).getASourceOverriddenMethod() and + not this.overrides(_) and + // guard against implicit overrides of default methods + not this.getAPossibleImplementationOfSrcMethod() != this and + // guard against interface implementations in inheriting subclasses + not exists(SrcMethod m | + 1 < strictcount(m.getAPossibleImplementationOfSrcMethod()) and + this = m.getAPossibleImplementationOfSrcMethod() + ) and + // UnsupportedOperationException could indicate that this is meant to be overridden + not exists(ClassInstanceExpr ex | + this.getBody().getLastStmt().(ThrowStmt).getExpr() = ex and + ex.getConstructedType().hasQualifiedName("java.lang", "UnsupportedOperationException") + ) and + // an unused parameter could indicate that this is meant to be overridden + forall(Parameter p | p = this.getAParameter() | exists(p.getAnAccess())) + } + + /** Gets a `MethodAccess` that calls this method. */ + MethodAccess getAnAccess() { + result.getMethod().getAPossibleImplementation() = this + } + } + + /** Holds if a call to `m` indicates that `m` is expected to return. */ + private predicate expectedReturn(EffectivelyNonVirtualMethod m) { + exists(Stmt s, Block b | + m.getAnAccess().getEnclosingStmt() = s and + b.getAStmt() = s and + not b.getLastStmt() = s + ) + } + + /** + * Gets a non-overridable method that always throws an exception or calls `exit`. + */ + private Method nonReturningMethod() { + result instanceof MethodExit or + not result.isOverridable() and + exists(Block body | + body = result.getBody() and + not exists(ReturnStmt ret | ret.getEnclosingCallable() = result) + | + not result.getReturnType() instanceof VoidType or + body.getLastStmt() = nonReturningStmt() + ) + } + + /** + * Gets a virtual method that always throws an exception or calls `exit`. + */ + private EffectivelyNonVirtualMethod likelyNonReturningMethod() { + result.getReturnType() instanceof VoidType and + not exists(ReturnStmt ret | ret.getEnclosingCallable() = result) and + not expectedReturn(result) and + forall(Parameter p | p = result.getAParameter() | exists(p.getAnAccess())) and + result.getBody().getLastStmt() = nonReturningStmt() + } + + /** + * Gets a `MethodAccess` that always throws an exception or calls `exit`. + */ + private MethodAccess nonReturningMethodAccess() { + result.getMethod().getSourceDeclaration() = nonReturningMethod() or + result = likelyNonReturningMethod().getAnAccess() + } + + /** + * Gets a statement that always throws an exception or calls `exit`. + */ + private Stmt nonReturningStmt() { + result instanceof ThrowStmt or + result.(ExprStmt).getExpr() = nonReturningMethodAccess() or + result.(Block).getLastStmt() = nonReturningStmt() or + exists(IfStmt ifstmt | ifstmt = result | + ifstmt.getThen() = nonReturningStmt() and + ifstmt.getElse() = nonReturningStmt() + ) or + exists(TryStmt try | try = result | + try.getBlock() = nonReturningStmt() and + forall(CatchClause cc | cc = try.getACatchClause() | cc.getBlock() = nonReturningStmt()) + ) + } + + /** + * Expressions and statements with CFG edges in post-order AST traversal. + * + * This includes most expressions, except those that initiate or propagate branching control + * flow (`LogicExpr`, `ConditionalExpr`), and parentheses, which aren't in the CFG. + * Only a few statements are included; those with specific side-effects + * occurring after the evaluation of their children, that is, `Call`, `ReturnStmt`, + * and `ThrowStmt`. CFG nodes without child nodes in the CFG that may complete + * normally are also included. + */ + private + class PostOrderNode extends ControlFlowNode { + PostOrderNode() { + // For VarAccess and ArrayAccess only read accesses (r-values) are included, + // as write accesses aren't included in the CFG. + this instanceof ArrayAccess and not exists(AssignExpr a | this = a.getDest()) or + this instanceof ArrayCreationExpr or + this instanceof ArrayInit or + this instanceof Assignment or + this instanceof BinaryExpr and not this instanceof LogicExpr or + this instanceof UnaryExpr and not this instanceof LogNotExpr or + this instanceof CastExpr or + this instanceof InstanceOfExpr or + this instanceof LocalVariableDeclExpr or + this instanceof RValue or + this instanceof Call or // includes both expressions and statements + this instanceof ReturnStmt or + this instanceof ThrowStmt or + this instanceof Literal or + this instanceof TypeLiteral or + this instanceof ThisAccess or + this instanceof SuperAccess or + this.(Block).getNumStmt() = 0 or + this instanceof SwitchCase or + this instanceof EmptyStmt or + this instanceof LocalClassDeclStmt or + this instanceof AssertStmt + } + + /** Gets child nodes in their order of execution. Indexing starts at either -1 or 0. */ + ControlFlowNode getChildNode(int index) { + exists(ArrayAccess e | e = this | + index = 0 and result = e.getArray() or + index = 1 and result = e.getIndexExpr() + ) or + exists(ArrayCreationExpr e | e = this | + result = e.getDimension(index) or + index = count(e.getADimension()) and result = e.getInit() + ) or + result = this.(ArrayInit).getInit(index) and index >= 0 or + exists(AssignExpr e, ArrayAccess lhs | e = this and lhs = e.getDest() | + index = 0 and result = lhs.getArray() or + index = 1 and result = lhs.getIndexExpr() or + index = 2 and result = e.getSource() + ) or + exists(AssignExpr e, VarAccess lhs | e = this and lhs = e.getDest() | + index = -1 and result = lhs.getQualifier() and not result instanceof TypeAccess or + index = 0 and result = e.getSource() + ) or + exists(AssignOp e | e = this | + index = 0 and result = e.getDest() or + index = 1 and result = e.getRhs() + ) or + exists(BinaryExpr e | e = this | + index = 0 and result = e.getLeftOperand() or + index = 1 and result = e.getRightOperand() + ) or + index = 0 and result = this.(UnaryExpr).getExpr() or + index = 0 and result = this.(CastExpr).getExpr() or + index = 0 and result = this.(InstanceOfExpr).getExpr() or + index = 0 and result = this.(LocalVariableDeclExpr).getInit() or + index = 0 and result = this.(RValue).getQualifier() and not result instanceof TypeAccess or + exists(Call e | e = this | + index = -1 and result = e.getQualifier() and not result instanceof TypeAccess or + result = e.getArgument(index) + ) or + index = 0 and result = this.(ReturnStmt).getResult() or + index = 0 and result = this.(ThrowStmt).getExpr() or + index = 0 and result = this.(AssertStmt).getExpr() + } + + /** Gets the first child node, if any. */ + ControlFlowNode firstChild() { + result = getChildNode(-1) or + result = getChildNode(0) and not exists(getChildNode(-1)) + } + + /** Holds if this CFG node has any child nodes. */ + predicate isLeafNode() { + not exists(getChildNode(_)) + } + + /** Holds if this node can finish with a `normalCompletion`. */ + predicate mayCompleteNormally() { + not this instanceof BooleanLiteral and + not this instanceof ReturnStmt and + not this instanceof ThrowStmt and + not this = nonReturningMethodAccess() + } + } + + /** + * If the body of `loop` finishes with `completion`, the loop will + * continue executing (provided the loop condition still holds). + */ + private predicate continues(Completion completion, LoopStmt loop) { + completion = NormalCompletion() or + // only consider continue completions if there actually is a `continue` + // somewhere inside this loop; we don't particularly care whether that + // `continue` could actually target this loop, we just want to restrict + // the size of the predicate + exists(ContinueStmt cnt | cnt.getParent+() = loop | + completion = anonymousContinueCompletion() or + completion = labelledContinueCompletion(getLabel(loop)) + ) + } + + /** + * Determine the part of the AST node `n` that will be executed first. + */ + private ControlFlowNode first(ControlFlowNode n) { + result = n and n instanceof LogicExpr or + result = n and n instanceof ConditionalExpr or + result = n and n.(PostOrderNode).isLeafNode() or + result = first(n.(PostOrderNode).firstChild()) or + result = first(n.(ParExpr).getExpr()) or + result = first(n.(SynchronizedStmt).getExpr()) or + result = n and n instanceof Stmt and + not n instanceof PostOrderNode and + not n instanceof SynchronizedStmt + } + + /** + * Bind `last` to a node inside the body of `try` that may finish with `completion` + * such that control will be transferred to a `catch` block or the `finally` block of `try`. + * + * In other words, `last` is either a resource declaration that throws, or a + * node in the `try` block that may not complete normally, or a node in + * the `try` block that has no control flow successors inside the block. + */ + private predicate catchOrFinallyCompletion(TryStmt try, ControlFlowNode last, Completion completion) { + last(try.getBlock(), last, completion) or + last(try.getAResource(), last, completion) and completion = ThrowCompletion(_) + } + + /** + * Bind `last` to a node inside the body of `try` that may finish with `completion` + * such that control may be transferred to the `finally` block (if it exists). + * + * In other words, if `last` throws an exception it is possibly not caught by any + * of the catch clauses. + */ + private predicate uncaught(TryStmt try, ControlFlowNode last, Completion completion) { + catchOrFinallyCompletion(try, last, completion) and + ( + exists(ThrowableType thrown | + thrown = thrownInBody(try) and completion = ThrowCompletion(thrown) and + not mustCatch(try.getACatchClause(), thrown) + ) or + completion = NormalCompletion() or completion = ReturnCompletion() or + completion = anonymousBreakCompletion() or completion = labelledBreakCompletion(_) or + completion = anonymousContinueCompletion() or completion = labelledContinueCompletion(_) + ) + } + + /** + * Bind `last` to a node inside `try` that may finish with `completion` such + * that control may be transferred to the `finally` block (if it exists). + * + * This is similar to `uncaught`, but also includes final statements of `catch` + * clauses. + */ + private predicate finallyPred(TryStmt try, ControlFlowNode last, Completion completion) { + uncaught(try, last, completion) or + last(try.getACatchClause(), last, completion) + } + + private predicate lastInFinally(TryStmt try, ControlFlowNode last) { + last(try.getFinally(), last, NormalCompletion()) + } + + /** + * Bind `last` to a cfg node nested inside `n` (or, indeed, `n` itself) such + * that `last` may be the last node during an execution of `n` and finish + * with the given completion. + * + * A `booleanCompletion` implies that `n` is an `Expr`. Any abnormal + * completion besides `throwCompletion` implies that `n` is a `Stmt`. + */ + private predicate last(ControlFlowNode n, ControlFlowNode last, Completion completion) { + // Exceptions are propagated from any sub-expression. + exists(Expr e | e.getParent() = n | last(e, last, completion) and completion = ThrowCompletion(_)) or + + // If an expression doesn't finish with a throw completion, then it executes normally with + // either a `normalCompletion` or a `booleanCompletion`. + + // A boolean completion in a non-boolean context just indicates a normal completion + // and a normal completion in a boolean context indicates an arbitrary boolean completion. + last(n, last, NormalCompletion()) and inBooleanContext(n) and completion = basicBooleanCompletion(_) or + last(n, last, BooleanCompletion(_, _)) and not inBooleanContext(n) and completion = NormalCompletion() or + + // Logic expressions and conditional expressions are executed in AST pre-order to facilitate + // proper short-circuit representation. All other expressions are executed in post-order. + + // The last node of a logic expression is either in the right operand with an arbitrary + // completion, or in the left operand with the corresponding boolean completion. + exists(AndLogicalExpr andexpr | andexpr = n | + last(andexpr.getLeftOperand(), last, completion) and completion = BooleanCompletion(false, _) or + last(andexpr.getRightOperand(), last, completion) + ) or + exists(OrLogicalExpr orexpr | orexpr = n | + last(orexpr.getLeftOperand(), last, completion) and completion = BooleanCompletion(true, _) or + last(orexpr.getRightOperand(), last, completion) + ) or + + // The last node of a `LogNotExpr` is in its sub-expression with an inverted boolean completion + // (or a `normalCompletion`). + exists(Completion subcompletion | last(n.(LogNotExpr).getExpr(), last, subcompletion) | + subcompletion = NormalCompletion() and completion = NormalCompletion() and not inBooleanContext(n) or + exists(boolean outervalue, boolean innervalue | + subcompletion = BooleanCompletion(outervalue, innervalue) and + completion = BooleanCompletion(outervalue.booleanNot(), innervalue) + ) + ) or + + // The last node of a `ConditionalExpr` is in either of its branches. + exists(ConditionalExpr condexpr | condexpr = n | + last(condexpr.getFalseExpr(), last, completion) or + last(condexpr.getTrueExpr(), last, completion) + ) or + + // Parentheses are skipped in the CFG. + last(n.(ParExpr).getExpr(), last, completion) or + + // The last node of a node executed in post-order is the node itself. + n.(PostOrderNode).mayCompleteNormally() and last = n and completion = NormalCompletion() or + + last = n and completion = basicBooleanCompletion(n.(BooleanLiteral).getBooleanValue()) or + + // The last statement in a block is any statement that does not complete normally, + // or the last statement. + exists(Block blk | blk = n | + last(blk.getAStmt(), last, completion) and completion != NormalCompletion() or + last(blk.getStmt(blk.getNumStmt()-1), last, completion) + ) or + + // The last node in an `if` statement is the last node in either of its branches or + // the last node of the condition with a false-completion in the absence of an else-branch. + exists(IfStmt ifstmt | ifstmt = n | + last(ifstmt.getCondition(), last, BooleanCompletion(false, _)) and completion = NormalCompletion() and not exists(ifstmt.getElse()) or + last(ifstmt.getThen(), last, completion) or + last(ifstmt.getElse(), last, completion) + ) or + + // A loop may terminate normally if its condition is false... + exists(LoopStmt loop | loop = n | + last(loop.getCondition(), last, BooleanCompletion(false, _)) and completion = NormalCompletion() or + // ...or if it's an enhanced for loop running out of items to iterate over... + // ...which may happen either immediately after the loop expression... + last(loop.(EnhancedForStmt).getExpr(), last, completion) and completion = NormalCompletion() or + exists(Completion bodyCompletion | last(loop.getBody(), last, bodyCompletion) | + // ...or after the last node in the loop's body in an iteration that would otherwise continue. + loop instanceof EnhancedForStmt and continues(bodyCompletion, loop) and completion = NormalCompletion() or + // Otherwise the last node is the last node in the loop's body... + // ...if it is an unlabelled `break` (causing the entire loop to complete normally) + (if bodyCompletion = anonymousBreakCompletion() then + completion = NormalCompletion() + // ...or if it is some other completion that does not continue the loop. + else + (not continues(bodyCompletion, loop) and completion = bodyCompletion)) + ) + ) or + + // `try` statements are a bit more complicated: + exists(TryStmt try | try = n | + // the last node in a `try` is the last node in its `finally` block + + // if the `finally` block completes normally, it resumes any completion that + // was current before the `finally` block was entered + lastInFinally(try, last) and + finallyPred(try, _, completion) + or + // otherwise, just take the completion of the `finally` block itself + last(try.getFinally(), last, completion) and + completion != NormalCompletion() + or + + // if there is no `finally` block, take the last node of the body or + // any of the `catch` clauses + not exists(try.getFinally()) and finallyPred(try, last, completion) + ) or + + // handle `switch` statements + exists(SwitchStmt switch | switch = n | + // unlabelled `break` causes the whole `switch` to complete normally + last(switch.getAStmt(), last, anonymousBreakCompletion()) and + completion = NormalCompletion() + or + // any other abnormal completion is propagated + last(switch.getAStmt(), last, completion) and + completion != anonymousBreakCompletion() and + completion != NormalCompletion() + or + // if the last case completes normally, then so does the switch + last(switch.getStmt(strictcount(switch.getAStmt())-1), last, NormalCompletion()) and + completion = NormalCompletion() + or + // if no default case exists, then normal completion of the expression may terminate the switch + not exists(switch.getDefaultCase()) and + last(switch.getExpr(), last, completion) and + completion = NormalCompletion() + ) or + + // the last statement of a synchronized statement is the last statement of its body + last(n.(SynchronizedStmt).getBlock(), last, completion) or + + // `return` statements give rise to a `Return` completion + last = (ReturnStmt)n and completion = ReturnCompletion() or + + // `throw` statements or throwing calls give rise to ` Throw` completion + exists(ThrowableType tt | mayThrow(n, tt) | last = n and completion = ThrowCompletion(tt)) or + + // `break` statements give rise to a `Break` completion + exists(BreakStmt break | break = n and last = n | + completion = labelledBreakCompletion(MkLabel(break.getLabel())) or + not exists(break.getLabel()) and completion = anonymousBreakCompletion() + ) or + + // `continue` statements give rise to a `Continue` completion + exists(ContinueStmt cont | cont = n and last = n | + completion = labelledContinueCompletion(MkLabel(cont.getLabel())) or + not exists(cont.getLabel()) and completion = anonymousContinueCompletion() + ) or + + // the last node in an `ExprStmt` is the last node in the expression + last(n.(ExprStmt).getExpr(), last, completion) and completion = NormalCompletion() or + + // the last statement of a labeled statement is the last statement of its body... + exists(LabeledStmt lbl, Completion bodyCompletion | lbl = n and last(lbl.getStmt(), last, bodyCompletion) | + // ...except if it's a `break` that refers to this labelled statement + if bodyCompletion = labelledBreakCompletion(MkLabel(lbl.getLabel())) then + completion = NormalCompletion() + else + completion = bodyCompletion + ) or + + // the last statement of a `catch` clause is the last statement of its block + last(n.(CatchClause).getBlock(), last, completion) or + + // the last node in a variable declaration statement is in the last of its individual declarations + exists(LocalVariableDeclStmt s | s = n | + last(s.getVariable(count(s.getAVariable())), last, completion) and completion = NormalCompletion() + ) + } + + /** + * Compute the intra-procedural successors of cfg node `n`, assuming its + * execution finishes with the given completion. + */ + cached + ControlFlowNode succ(ControlFlowNode n, Completion completion) { + // Callables serve as their own exit nodes. + exists(Callable c | last(c.getBody(), n, completion) | result = c) or + + // Logic expressions and conditional expressions execute in AST pre-order. + completion = NormalCompletion() and + (result = first(n.(AndLogicalExpr).getLeftOperand()) or + result = first(n.(OrLogicalExpr).getLeftOperand()) or + result = first(n.(LogNotExpr).getExpr()) or + result = first(n.(ConditionalExpr).getCondition())) or + + // If a logic expression doesn't short-circuit then control flows from its left operand to its right. + exists(AndLogicalExpr e | + last(e.getLeftOperand(), n, completion) and completion = BooleanCompletion(true, _) and + result = first(e.getRightOperand()) + ) or + exists(OrLogicalExpr e | + last(e.getLeftOperand(), n, completion) and completion = BooleanCompletion(false, _) and + result = first(e.getRightOperand()) + ) or + + // Control flows to the corresponding branch depending on the boolean completion of the condition. + exists(ConditionalExpr e | + last(e.getCondition(), n, completion) and completion = BooleanCompletion(true, _) and result = first(e.getTrueExpr()) or + last(e.getCondition(), n, completion) and completion = BooleanCompletion(false, _) and result = first(e.getFalseExpr()) + ) or + + // In other expressions control flows from left to right and ends in the node itself. + exists(PostOrderNode p, int i | last(p.getChildNode(i), n, completion) and completion = NormalCompletion() | + result = first(p.getChildNode(i+1)) or + not exists(p.getChildNode(i+1)) and result = p + ) or + + // Statements within a block execute sequentially. + result = first(n.(Block).getStmt(0)) and completion = NormalCompletion() or + exists(Block blk, int i | + last(blk.getStmt(i), n, completion) and completion = NormalCompletion() and result = first(blk.getStmt(i+1)) + ) or + + // Control flows to the corresponding branch depending on the boolean completion of the condition. + exists(IfStmt s | + n = s and result = first(s.getCondition()) and completion = NormalCompletion() or + last(s.getCondition(), n, completion) and completion = BooleanCompletion(true, _) and result = first(s.getThen()) or + last(s.getCondition(), n, completion) and completion = BooleanCompletion(false, _) and result = first(s.getElse()) + ) or + + // For statements: + exists(ForStmt for, ControlFlowNode condentry | + // Any part of the control flow that aims for the condition needs to hit either the condition... + condentry = first(for.getCondition()) or + // ...or the body if the for doesn't include a condition. + not exists(for.getCondition()) and condentry = first(for.getStmt()) + | + // From the entry point, which is the for statement itself, control goes to either the first init expression... + n = for and result = first(for.getInit(0)) and completion = NormalCompletion() or + // ...or the condition if the for doesn't include init expressions. + n = for and not exists(for.getAnInit()) and result = condentry and completion = NormalCompletion() or + // Init expressions execute sequentially, after which control is transferred to the condition. + exists(int i | last(for.getInit(i), n, completion) and completion = NormalCompletion() | + result = first(for.getInit(i+1)) or + not exists(for.getInit(i+1)) and result = condentry + ) or + // The true-successor of the condition is the body of the for loop. + last(for.getCondition(), n, completion) and completion = BooleanCompletion(true, _) and result = first(for.getStmt()) or + // The updates execute sequentially, after which control is transferred to the condition. + exists(int i | last(for.getUpdate(i), n, completion) and completion = NormalCompletion() | + result = first(for.getUpdate(i+1)) or + not exists(for.getUpdate(i+1)) and result = condentry + ) or + // The back edge of the loop: control goes to either the first update or the condition if no updates exist. + last(for.getStmt(), n, completion) and continues(completion, for) and + (result = first(for.getUpdate(0)) or + result = condentry and not exists(for.getAnUpdate())) + ) or + + // Enhanced for statements: + exists(EnhancedForStmt for | + // First the expression gets evaluated... + n = for and result = first(for.getExpr()) and completion = NormalCompletion() or + // ...then the variable gets assigned... + last(for.getExpr(), n, completion) and completion = NormalCompletion() and result = for.getVariable() or + // ...and then control goes to the body of the loop. + n = for.getVariable() and result = first(for.getStmt()) and completion = NormalCompletion() or + // Finally, the back edge of the loop goes to reassign the variable. + last(for.getStmt(), n, completion) and continues(completion, for) and result = for.getVariable() + ) or + + // While loops start at the condition... + result = first(n.(WhileStmt).getCondition()) and completion = NormalCompletion() or + // ...and do-while loops start at the body. + result = first(n.(DoStmt).getStmt()) and completion = NormalCompletion() or + exists(LoopStmt loop | loop instanceof WhileStmt or loop instanceof DoStmt | + // Control goes from the condition via a true-completion to the body... + last(loop.getCondition(), n, completion) and completion = BooleanCompletion(true, _) and result = first(loop.getBody()) or + // ...and through the back edge from the body back to the condition. + last(loop.getBody(), n, completion) and continues(completion, loop) and result = first(loop.getCondition()) + ) or + + // Resource declarations in a try-with-resources execute sequentially. + exists(TryStmt try, int i | last(try.getResource(i), n, completion) and completion = NormalCompletion() | + result = first(try.getResource(i+1)) or + not exists(try.getResource(i+1)) and result = first(try.getBlock()) + ) or + + // After the last resource declaration, control transfers to the body. + exists(TryStmt try | n = try and completion = NormalCompletion() | + result = first(try.getResource(0)) or + not exists(try.getAResource()) and result = first(try.getBlock()) + ) or + + // exceptional control flow + exists(TryStmt try | catchOrFinallyCompletion(try, n, completion) | + // if the body of the `try` throws... + exists(ThrowableType tt | completion = ThrowCompletion(tt) | + // ...control transfers to a catch clause... + result = first(handlingCatchClause(try, tt)) or + // ...or to the finally block + not mustCatch(try.getACatchClause(), tt) and result = first(try.getFinally()) + ) or + + // if the body completes normally, control transfers to the finally block + not completion = ThrowCompletion(_) and result = first(try.getFinally()) + ) or + + // after each catch clause, control transfers to the finally block + exists(TryStmt try | last(try.getACatchClause(), n, completion) | + result = first(try.getFinally()) + ) or + + // Catch clauses first assign their variable and then execute their block + exists(CatchClause cc | completion = NormalCompletion() | + n = cc and result = first(cc.getVariable()) or + last(cc.getVariable(), n, completion) and result = first(cc.getBlock()) + ) or + + // Switch statements + exists(SwitchStmt switch | completion = NormalCompletion() | + // From the entry point control is transferred first to the expression... + n = switch and result = first(switch.getExpr()) or + // ...and then to one of the cases. + last(switch.getExpr(), n, completion) and result = first(switch.getACase()) or + // Statements within a switch body execute sequentially. + exists(int i | last(switch.getStmt(i), n, completion) and result = first(switch.getStmt(i+1))) + ) or + + // No edges in a SwitchCase - the constant expression in a ConstCase isn't included in the CFG. + + // Synchronized statements execute their expression _before_ synchronization, so the CFG reflects that. + exists(SynchronizedStmt synch | completion = NormalCompletion() | + last(synch.getExpr(), n, completion) and result = synch or + n = synch and result = first(synch.getBlock()) + ) or + + result = first(n.(ExprStmt).getExpr()) and completion = NormalCompletion() or + + result = first(n.(LabeledStmt).getStmt()) and completion = NormalCompletion() or + + // Variable declarations in a variable declaration statement are executed sequentially. + exists(LocalVariableDeclStmt s | completion = NormalCompletion() | + n = s and result = first(s.getVariable(1)) or + exists(int i | last(s.getVariable(i), n, completion) and result = first(s.getVariable(i+1))) + ) + } + + /* + * Conditions give rise to nodes with two normal successors, a true successor + * and a false successor. At least one of them is completely contained in the + * AST node of the branching construct and is therefore tagged with the + * corresponding `booleanCompletion` in the `succ` relation (for example, the + * then-branch of an if-statement, or the right operand of a binary logic + * expression). The other successor may be tagged with either the corresponding + * `booleanCompletion` (for example in an if-statement with an else-branch or + * in a `ConditionalExpr`) or a `normalCompletion` (for example in an + * if-statement without an else-part). + * + * If the other successor ends a finally block it may also be tagged with an + * abnormal completion instead of a `normalCompletion`. This completion is + * calculated by the `resumption` predicate. In this case the successor might + * no longer be unique, as there can be multiple completions to be resumed + * after the finally block. + */ + + /** + * Gets the _resumption_ for cfg node `n`, that is, the completion according + * to which control flow is determined if `n` completes normally. + * + * In most cases, the resumption is simply the normal completion, except if + * `n` is the last node of a `finally` block, in which case it is the + * completion of any predecessors of the `finally` block as determined by + * predicate `finallyPred`, since their completion is resumed after normal + * completion of the `finally`. + */ + private Completion resumption(ControlFlowNode n) { + exists(TryStmt try | lastInFinally(try, n) and finallyPred(try, _, result)) or + not lastInFinally(_, n) and result = NormalCompletion() + } + + /** + * A true- or false-successor that is tagged with the corresponding `booleanCompletion`. + * + * That is, the `booleanCompletion` is the label of the edge in the CFG. + */ + private ControlFlowNode mainBranchSucc(ControlFlowNode n, boolean b) { + result = succ(n, BooleanCompletion(_, b)) + } + + /** + * A true- or false-successor that is not tagged with a `booleanCompletion`. + * + * That is, the label of the edge in the CFG is a `normalCompletion` or + * some other completion if `n` occurs as the last node in a finally block. + * + * In the latter case, when `n` occurs as the last node in a finally block, there might be + * multiple different such successors. + */ + private ControlFlowNode otherBranchSucc(ControlFlowNode n, boolean b) { + exists(ControlFlowNode main | main = mainBranchSucc(n, b.booleanNot()) | + result = succ(n, resumption(n)) and + not result = main and + (b = true or b = false) + ) + } + + /** Gets a true- or false-successor of `n`. */ + cached + ControlFlowNode branchSuccessor(ControlFlowNode n, boolean branch) { + result = mainBranchSucc(n, branch) or + result = otherBranchSucc(n, branch) + } + +} +private import ControlFlowGraphImpl + +/** A control-flow node that branches based on a condition. */ +class ConditionNode extends ControlFlowNode { + ConditionNode() { + exists(branchSuccessor(this, _)) + } + + /** Gets a true- or false-successor of the `ConditionNode`. */ + ControlFlowNode getABranchSuccessor(boolean branch) { + result = branchSuccessor(this, branch) + } + + /** Gets a true-successor of the `ConditionNode`. */ + ControlFlowNode getATrueSuccessor() { + result = getABranchSuccessor(true) + } + + /** Gets a false-successor of the `ConditionNode`. */ + ControlFlowNode getAFalseSuccessor() { + result = getABranchSuccessor(false) + } + + /** Gets the condition of this `ConditionNode`. This is equal to the node itself. */ + Expr getCondition() { + result = this + } +} diff --git a/java/ql/src/semmle/code/java/Conversions.qll b/java/ql/src/semmle/code/java/Conversions.qll new file mode 100644 index 00000000000..2916b8b5404 --- /dev/null +++ b/java/ql/src/semmle/code/java/Conversions.qll @@ -0,0 +1,172 @@ +/** + * Provides support for conversion contexts, in which an expression is converted + * (implicitly or explicitly) to a different type. + * + * See the Java Language Specification, Section 5, for details. + */ +import java +import semmle.code.java.arithmetic.Overflow + +/** + * A expression where an implicit conversion can occur. + * + * See the Java Language Specification, Section 5. + */ +abstract class ConversionSite extends Expr { + /** + * Gets the type that is converted to. + */ + abstract Type getConversionTarget(); + + /** + * Gets the type that is converted from. + */ + Type getConversionSource() { + result = this.getType() + } + + /** + * Whether this conversion site actually induces a conversion. + */ + predicate isTrivial() { + getConversionTarget() = getConversionSource() + } + + /** + * Whether this conversion is implicit. + */ + predicate isImplicit() { + any() + } + + abstract string kind(); +} + +/** + * An assignment conversion. For example, `x += b` converts `b` + * to be of the type of `x`. + * + * See the Java Language Specification, Section 5.2. + */ +class AssignmentConversionContext extends ConversionSite { + Variable v; + AssignmentConversionContext() { + this = v.getAnAssignedValue() or + exists(Assignment a | a.getDest().getProperExpr() = v.getAnAccess() and this = a.getSource()) + } + + override Type getConversionTarget() { + result = v.getType() + } + + override string kind() { + result = "assignment context" + } +} + +/** + * An return conversion. For example, `return b` converts `b` + * to be of the return type of the enclosing callable. + * + * Note that the Java Language Specification handles these as + * assignment conversions (section 5.2), but for clarity we split them out here. + */ +class ReturnConversionSite extends ConversionSite { + ReturnStmt r; + ReturnConversionSite() { + this = r.getResult() + } + + override Type getConversionTarget() { + result = r.getEnclosingCallable().getReturnType() + } + override string kind() { + result = "return context" + } +} + +/** + * An invocation conversion. For example `f(b)` converts `b` to + * have the type of the corresponding parameter of `f`. + * + * See the Java Language Specification, Section 5.3. + */ +class InvocationConversionContext extends ConversionSite { + Call c; + int index; + InvocationConversionContext() { + this = c.getArgument(index) + } + + override Type getConversionTarget() { + result = c.getCallee().getParameter(index).getType() + } + + override string kind() { + result = "invocation context" + } +} + +/** + * A string conversion. For example `a + b`, where `a` is a + * `String`, converts `b` to have type `String`. + * + * See the Java Language Specification, Section 5.4. + */ +class StringConversionContext extends ConversionSite { + AddExpr a; + StringConversionContext() { + a.getAnOperand() = this and + not this.getType() instanceof TypeString and + a.getAnOperand().getType() instanceof TypeString + } + + override Type getConversionTarget() { + result instanceof TypeString + } + + override string kind() { + result = "string context" + } +} + +class CastConversionContext extends ConversionSite { + CastExpr c; + CastConversionContext() { + this = c.getExpr() + } + + override Type getConversionTarget() { + result = c.getType() + } + + override predicate isImplicit() { + none() + } + + override string kind() { + result = "cast context" + } +} + +/** + * A numeric conversion. For example, `a * b` converts `a` and + * `b` to have an appropriate numeric type. + * + * See the Java Language Specification, Section 5.4. + */ +class NumericConversionContext extends ConversionSite { + ArithExpr e; + NumericConversionContext() { + this = e.getAnOperand() + } + + override Type getConversionTarget() { + result = e.getType() + } + + override string kind() { + result = "numeric context" + } + +} diff --git a/java/ql/src/semmle/code/java/Dependency.qll b/java/ql/src/semmle/code/java/Dependency.qll new file mode 100755 index 00000000000..a682b115149 --- /dev/null +++ b/java/ql/src/semmle/code/java/Dependency.qll @@ -0,0 +1,117 @@ +/** + * Provides utility predicates for representing dependencies between types. + */ + +import Type +import Generics +import Expr + +/** + * Holds if type `t` depends on type `dep`. + * + * Dependencies are restricted to generic and non-generic reference types. + * + * Dependencies on parameterized or raw types are decomposed into + * a dependency on the corresponding generic type and separate + * dependencies on (source declarations of) any type arguments. + * + * For example, a dependency on type `List>` is represented by + * dependencies on the generic types `List` and `Set` as well as a dependency + * on the type `String` but not on the parameterized types `List>` + * or `Set`. + */ +predicate depends(RefType t, RefType dep) { + // Type `t` is neither a parameterized nor a raw type and is distinct from `dep`. + not isParameterized(t) and + not isRaw(t) and + not t = dep and + // Type `t` depends on: + ( + // its supertypes, + usesType(t.getASupertype(), dep) + or + // its enclosing type, + usesType(t.(NestedType).getEnclosingType(), dep) + or + // the type of any field declared in `t`, + exists(Field f | f.getDeclaringType() = t | + usesType(f.getType(), dep) + ) or + // the return type of any method declared in `t`, + exists(Method m | m.getDeclaringType() = t | + usesType(m.getReturnType(), dep) + ) or + // the type of any parameter of a callable in `t`, + exists(Callable c | c.getDeclaringType() = t | + usesType(c.getAParamType(), dep) + ) or + // the type of any exception in the `throws` clause of a callable declared in `t`, + exists(Exception e | e.getCallable().getDeclaringType() = t | + usesType(e.getType(), dep) + ) or + // the declaring type of a callable accessed in `t`, + exists(Callable c | + c.getAReference().getEnclosingCallable().getDeclaringType() = t + | + usesType(c.getSourceDeclaration().getDeclaringType(), dep) + ) or + // the declaring type of a field accessed in `t`, + exists(Field f | + f.getAnAccess().getEnclosingCallable().getDeclaringType() = t + | + usesType(f.getSourceDeclaration().getDeclaringType(), dep) + ) or + // the type of a local variable declared in `t`, + exists(LocalVariableDeclExpr decl | + decl.getEnclosingCallable().getDeclaringType() = t + | + usesType(decl.getType(), dep) + ) or + // the type of a type literal accessed in `t`, + exists(TypeLiteral l | + l.getEnclosingCallable().getDeclaringType() = t + | + usesType(l.getTypeName().getType(), dep) + ) or + // the type of an annotation (or one of its element values) that annotates `t` or one of its members, + exists(Annotation a | + a.getAnnotatedElement() = t or + a.getAnnotatedElement().(Member).getDeclaringType() = t + | + usesType(a.getType(), dep) or + usesType(a.getAValue().getType(), dep) + ) or + // the type accessed in an `instanceof` expression in `t`. + exists(InstanceOfExpr ioe | + t = ioe.getEnclosingCallable().getDeclaringType() + | + usesType(ioe.getTypeName().getType(), dep) + ) + ) +} + +/** + * Bind the reference type `dep` to the source declaration of any types used to construct `t`, + * including (possibly nested) type parameters of parameterized types, element types of array types, + * and bounds of type variables or wildcards. + */ +cached +predicate usesType(Type t, RefType dep) { + dep = inside*(t).getSourceDeclaration() and + not dep instanceof TypeVariable and + not dep instanceof Wildcard and + not dep instanceof Array +} + +/** + * Gets a type argument of a parameterized type, + * the element type of an array type, or + * a bound of a type variable or wildcard. + */ +private +RefType inside(Type t) { + result = t.(TypeVariable).getATypeBound().getType() or + result = t.(Wildcard).getATypeBound().getType() or + result = t.(ParameterizedType).getATypeArgument() or + result = t.(Array).getElementType() +} diff --git a/java/ql/src/semmle/code/java/DependencyCounts.qll b/java/ql/src/semmle/code/java/DependencyCounts.qll new file mode 100644 index 00000000000..fefa16ab216 --- /dev/null +++ b/java/ql/src/semmle/code/java/DependencyCounts.qll @@ -0,0 +1,150 @@ +/** + * This library provides utility predicates for representing the number of dependencies between types. + */ + +import Type +import Generics +import Expr + +/** + * The number of dependencies from type `t` on type `dep`. + * + * Dependencies are restricted to generic and non-generic reference types. + * + * Dependencies on parameterized or raw types are decomposed into + * a dependency on the corresponding generic type and separate + * dependencies on (source declarations of) any type arguments. + * + * For example, a dependency on type `List>` is represented by + * dependencies on the generic types `List` and `Set` as well as a dependency + * on the type `String` but not on the parameterized types `List>` + * or `Set`. + */ +pragma[nomagic] +predicate numDepends(RefType t, RefType dep, int value) { + // Type `t` is neither a parameterized nor a raw type and is distinct from `dep`. + not isParameterized(t) and + not isRaw(t) and + not t = dep and + // Type `t` depends on: + value = strictcount(Element elem | + // its supertypes, + exists(RefType s | elem = s and t.hasSupertype(s) | + usesType(s, dep) + ) or + // its enclosing types, + exists(RefType s | elem = s and t.getEnclosingType() = s | + usesType(s, dep) + ) or + // the type of any field declared in `t`, + exists(Field f | elem = f and f.getDeclaringType() = t | + usesType(f.getType(), dep) + ) or + // the return type of any method declared in `t`, + exists(Method m | elem = m and m.getDeclaringType() = t | + usesType(m.getReturnType(), dep) + ) or + // the type of any parameter of a callable in `t`, + exists(Parameter p | elem = p and p.getCallable().getDeclaringType() = t | + usesType(p.getType(), dep) + ) or + // the type of any exception in the `throws` clause of a callable declared in `t`, + exists(Exception e | elem = e and e.getCallable().getDeclaringType() = t | + usesType(e.getType(), dep) + ) or + // the declaring type of a callable accessed in `t`, + exists(Call c | elem = c and + c.getEnclosingCallable().getDeclaringType() = t + | + usesType(c.getCallee().getSourceDeclaration().getDeclaringType(), dep) + ) or + // the declaring type of a field accessed in `t`, + exists(FieldAccess fa | elem = fa and + fa.getEnclosingCallable().getDeclaringType() = t + | + usesType(fa.getField().getSourceDeclaration().getDeclaringType(), dep) + ) or + // the type of a local variable declared in `t`, + exists(LocalVariableDeclExpr decl | elem = decl and + decl.getEnclosingCallable().getDeclaringType() = t + | + usesType(decl.getType(), dep) + ) or + // the type of a type literal accessed in `t`, + exists(TypeLiteral l | elem = l and + l.getEnclosingCallable().getDeclaringType() = t + | + usesType(l.getTypeName().getType(), dep) + ) or + // the type of an annotation (or one of its element values) that annotates `t` or one of its members, + exists(Annotation a | + a.getAnnotatedElement() = t or + a.getAnnotatedElement().(Member).getDeclaringType() = t + | + elem = a and usesType(a.getType(), dep) or + elem = a.getAValue() and elem.getFile().getExtension() = "java" and usesType(elem.(Expr).getType(), dep) + ) or + // the type accessed in an `instanceof` expression in `t`. + exists(InstanceOfExpr ioe | elem = ioe and + t = ioe.getEnclosingCallable().getDeclaringType() + | + usesType(ioe.getTypeName().getType(), dep) + ) + ) +} + +predicate filePackageDependencyCount(File sourceFile, int total, string entity) { + exists(Package targetPackage | + total = strictsum(RefType sourceType, RefType targetType, int num | + sourceType.getFile() = sourceFile and + sourceType.fromSource() and + sourceType.getPackage() != targetPackage and + targetType.getPackage() = targetPackage and + numDepends(sourceType, targetType, num) + | + num) + and + entity = "/" + sourceFile.getRelativePath() + "<|>" + targetPackage + "<|>N/A" + ) +} + +private string nameVersionRegex() { + result = "([_.A-Za-z0-9-]*)-([0-9][A-Za-z0-9.+_-]*)" +} + +/** + * Given a JAR filename, try to split it into a name and version. + * This is a heuristic approach assuming that the a dash is used to + * separate the library name from a largely numeric version such as + * `commons-io-2.4`. + */ +bindingset[target] +predicate hasDashedVersion(string target, string name, string version) { + exists(string regex | regex = nameVersionRegex() | + name = target.regexpCapture(regex, 1) and + version = target.regexpCapture(regex, 2)) +} + +predicate fileJarDependencyCount(File sourceFile, int total, string entity) { + exists(Container targetJar, string jarStem | + jarStem = targetJar.getStem() and + targetJar.(File).getExtension() = "jar" and + jarStem != "rt" + | + total = strictsum(RefType r, RefType dep, int num | + r.getFile() = sourceFile and + r.fromSource() and + dep.getFile().getParentContainer*() = targetJar and + numDepends(r, dep, num) + | + num) and + exists(string name, string version | + if hasDashedVersion(jarStem, _, _) then + hasDashedVersion(jarStem, name, version) + else + (name = jarStem and version = "unknown") + | + entity = "/" + sourceFile.getRelativePath() + "<|>" + name + "<|>" + version + ) + ) +} diff --git a/java/ql/src/semmle/code/java/Element.qll b/java/ql/src/semmle/code/java/Element.qll new file mode 100755 index 00000000000..f4496a0bc93 --- /dev/null +++ b/java/ql/src/semmle/code/java/Element.qll @@ -0,0 +1,61 @@ +/** + * Provides a class that represents named elements in Java programs. + */ + +import CompilationUnit +import semmle.code.Location +import Javadoc + +/** A program element that has a name. */ +class Element extends @element, Top { + /** Holds if this element has the specified `name`. */ + predicate hasName(string name) { hasName(this,name) } + + /** Gets the name of this element. */ + string getName() { this.hasName(result) } + + /** + * Holds if this element transitively contains the specified element `e`. + */ + predicate contains(Element e) { this.hasChildElement+(e) } + + /** + * Holds if this element is the immediate parent of the specified element `e`. + * + * It is usually preferable to use more specific predicates such as + * `getEnclosingCallable()`, `getDeclaringType()` and/or `getEnclosingType()` + * instead of this general predicate. + */ + predicate hasChildElement(Element e) { hasChildElement(this, e) } + + /** + * Holds if this element pertains to a source file. + * + * Elements pertaining to source files may include generated elements + * not visible in source code, such as implicit default constructors. + */ + predicate fromSource() { + getCompilationUnit().getExtension() = "java" + } + + /** Gets the compilation unit that this element belongs to. */ + CompilationUnit getCompilationUnit() { result = getFile() } + + /** Cast this element to a `Documentable`. */ + Documentable getDoc() { result = this } +} + +/** + * Holds if element `parent` is immediately above element `e` in the syntax tree. + */ +private predicate hasChildElement(Element parent, Element e) { + cupackage(e,parent) or + enclInReftype(e,parent) or + (not(enclInReftype(e,_)) and e.(Class).getCompilationUnit() = parent) or + (not(enclInReftype(e,_)) and e.(Interface).getCompilationUnit() = parent) or + methods(e,_,_,_,parent,_) or + constrs(e,_,_,_,parent,_) or + params(e,_,_,parent,_) or + fields(e,_,_,parent,_) or + typeVars(e,_,_,_,parent) +} diff --git a/java/ql/src/semmle/code/java/Exception.qll b/java/ql/src/semmle/code/java/Exception.qll new file mode 100755 index 00000000000..1b4e0ba85a2 --- /dev/null +++ b/java/ql/src/semmle/code/java/Exception.qll @@ -0,0 +1,30 @@ +/** + * Provides classes and predicates for working with Java exceptions. + */ + +import Element +import Type + +/** + * An Exception represents an element listed in the `throws` clause + * of a method of constructor. + * + * For example, `E` is an exception thrown by method `m` in + * `void m() throws E;`, whereas `T` is an exception _type_ in + * `class T extends Exception { }`. + */ +class Exception extends Element, @exception { + /** Gets the type of this exception. */ + RefType getType() { exceptions(this,result,_) } + + /** Gets the callable whose `throws` clause contains this exception. */ + Callable getCallable() { exceptions(this,_,result) } + + /** Gets the name of this exception, that is, the name of its type. */ + override string getName() { result = this.getType().getName() } + + /** Holds if this exception has the specified `name`. */ + override predicate hasName(string name) { this.getType().hasName(name) } + + override string toString() { result = this.getType().toString() } +} diff --git a/java/ql/src/semmle/code/java/Expr.qll b/java/ql/src/semmle/code/java/Expr.qll new file mode 100755 index 00000000000..64eac31dff3 --- /dev/null +++ b/java/ql/src/semmle/code/java/Expr.qll @@ -0,0 +1,1663 @@ +/** + * Provides classes for working with Java expressions. + */ + +import Member +import Type +import Variable +import Statement + +/** A common super-class that represents all kinds of expressions. */ +class Expr extends ExprParent, @expr { + /*abstract*/ override string toString() { result = "expr" } + + /** + * Gets the callable in which this expression occurs, if any. + */ + Callable getEnclosingCallable() { + callableEnclosingExpr(this,result) + } + + /** Gets the index of this expression as a child of its parent. */ + int getIndex() { exprs(this,_,_,_,result) } + + /** Gets the parent of this expression. */ + ExprParent getParent() { exprs(this,_,_,result,_) } + + /** Holds if this expression is the child of the specified parent at the specified (zero-based) position. */ + predicate isNthChildOf(ExprParent parent, int index) { + exprs(this,_,_,parent,index) + } + + /** Gets the type of this expression. */ + Type getType() { exprs(this,_,result,_,_) } + + /** Gets the compilation unit in which this expression occurs. */ + CompilationUnit getCompilationUnit() { result = this.getFile() } + + /** + * Gets the kind of this expression. + * + * Each kind of expression has a unique (integer) identifier. + * This is an implementation detail that should not normally + * be referred to by library users, since the kind of an expression + * is also represented by its QL type. + * + * In a few rare situations, referring to the kind of an expression + * via its unique identifier may be appropriate; for example, when + * comparing whether two expressions have the same kind (as opposed + * to checking whether an expression has a particular kind). + */ + int getKind() { exprs(this,result,_,_,_) } + + /** Gets this expression with any surrounding parentheses removed. */ + Expr getProperExpr() { + result = this.(ParExpr).getExpr().getProperExpr() or + result = this and not this instanceof ParExpr + } + + /** Gets the statement containing this expression, if any. */ + Stmt getEnclosingStmt() { + statementEnclosingExpr(this,result) + } + + /** Gets a child of this expression. */ + Expr getAChildExpr() { exprs(result,_,_,this,_) } + + /** Gets the basic block in which this expression occurs, if any. */ + BasicBlock getBasicBlock() { result.getANode() = this } + + /** Gets the `ControlFlowNode` corresponding to this expression. */ + ControlFlowNode getControlFlowNode() { result = this } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + string getHalsteadID() { result = this.toString() } + + /** + * Holds if this expression is a compile-time constant. + * + * See JLS v8, section 15.28 (Constant Expressions). + */ + predicate isCompileTimeConstant() { this instanceof CompileTimeConstantExpr } + + /** Holds if this expression occurs in a static context. */ + predicate isInStaticContext() { + /* + * JLS 8.1.3 (Inner Classes and Enclosing Instances) + * A statement or expression occurs in a static context if and only if the + * innermost method, constructor, instance initializer, static initializer, + * field initializer, or explicit constructor invocation statement + * enclosing the statement or expression is a static method, a static + * initializer, the variable initializer of a static variable, or an + * explicit constructor invocation statement. + */ + getEnclosingCallable().isStatic() or + getParent+() instanceof ThisConstructorInvocationStmt or + getParent+() instanceof SuperConstructorInvocationStmt or + exists(LambdaExpr lam | lam.asMethod() = getEnclosingCallable() and lam.isInStaticContext()) + } +} + +/** + * Holds if the specified type is either a primitive type or type `String`. + * + * Auxiliary predicate used by `CompileTimeConstantExpr`. + */ +private +predicate primitiveOrString(Type t) { + t instanceof PrimitiveType or + t instanceof TypeString +} + +/** + * A compile-time constant expression. + * + * See JLS v8, section 15.28 (Constant Expressions). + */ +class CompileTimeConstantExpr extends Expr { + CompileTimeConstantExpr() { + primitiveOrString(getType()) and ( + // Literals of primitive type and literals of type `String`. + this instanceof Literal or + // Casts to primitive types and casts to type `String`. + this.(CastExpr).getExpr().isCompileTimeConstant() or + // The unary operators `+`, `-`, `~`, and `!` (but not `++` or `--`). + this.(PlusExpr).getExpr().isCompileTimeConstant() or + this.(MinusExpr).getExpr().isCompileTimeConstant() or + this.(BitNotExpr).getExpr().isCompileTimeConstant() or + this.(LogNotExpr).getExpr().isCompileTimeConstant() or + // The multiplicative operators `*`, `/`, and `%`, + // the additive operators `+` and `-`, + // the shift operators `<<`, `>>`, and `>>>`, + // the relational operators `<`, `<=`, `>`, and `>=` (but not `instanceof`), + // the equality operators `==` and `!=`, + // the bitwise and logical operators `&`, `^`, and `|`, + // the conditional-and operator `&&` and the conditional-or operator `||`. + // These are precisely the operators represented by `BinaryExpr`. + ( + this.(BinaryExpr).getLeftOperand().isCompileTimeConstant() and + this.(BinaryExpr).getRightOperand().isCompileTimeConstant() + ) or + // The ternary conditional operator ` ? : `. + exists(ConditionalExpr e | this = e | + e.getCondition().isCompileTimeConstant() and + e.getTrueExpr().isCompileTimeConstant() and + e.getFalseExpr().isCompileTimeConstant() + ) or + // Parenthesized expressions whose contained expression is a constant expression. + this.(ParExpr).getExpr().isCompileTimeConstant() or + // Access to a final variable initialized by a compile-time constant. + exists(Variable v | this = v.getAnAccess() | + v.isFinal() and + v.getInitializer().isCompileTimeConstant() + ) + ) + } + + /** + * Gets the string value of this expression, where possible. + */ + string getStringValue() { + result = this.(StringLiteral).getRepresentedString() + or + result = this.(ParExpr).getExpr().(CompileTimeConstantExpr).getStringValue() + or + result = this.(AddExpr).getLeftOperand().(CompileTimeConstantExpr).getStringValue() + + this.(AddExpr).getRightOperand().(CompileTimeConstantExpr).getStringValue() + or + // Ternary conditional, with compile-time constant condition. + exists(ConditionalExpr ce, boolean condition | + ce = this and + condition = ce.getCondition().(CompileTimeConstantExpr).getBooleanValue() + | + if condition = true then + result = ce.getTrueExpr().(CompileTimeConstantExpr).getStringValue() + else + result = ce.getFalseExpr().(CompileTimeConstantExpr).getStringValue() + ) or + exists(Variable v | this = v.getAnAccess() | + result = v.getInitializer().(CompileTimeConstantExpr).getStringValue() + ) + } + + /** + * Gets the boolean value of this expression, where possible. + */ + boolean getBooleanValue() { + // Literal value. + result = this.(BooleanLiteral).getBooleanValue() or + // No casts relevant to booleans. + // `!` is the only unary operator that evaluates to a boolean. + result = this.(LogNotExpr).getExpr().(CompileTimeConstantExpr).getBooleanValue().booleanNot() or + // Handle binary expressions that have integer operands and a boolean result. + exists(BinaryExpr b, int left, int right | + b = this and + left = b.getLeftOperand().(CompileTimeConstantExpr).getIntValue() and + right = b.getRightOperand().(CompileTimeConstantExpr).getIntValue() + | + (b instanceof LTExpr and if left < right then result = true else result = false) or + (b instanceof LEExpr and if left <= right then result = true else result = false) or + (b instanceof GTExpr and if left > right then result = true else result = false) or + (b instanceof GEExpr and if left >= right then result = true else result = false) or + (b instanceof EQExpr and if left = right then result = true else result = false) or + (b instanceof NEExpr and if left != right then result = true else result = false) + ) or + // Handle binary expressions that have boolean operands and a boolean result. + exists(BinaryExpr b, boolean left, boolean right | + b = this and + left = b.getLeftOperand().(CompileTimeConstantExpr).getBooleanValue() and + right = b.getRightOperand().(CompileTimeConstantExpr).getBooleanValue() + | + (b instanceof EQExpr and if left = right then result = true else result = false) or + (b instanceof NEExpr and if left != right then result = true else result = false) or + ((b instanceof AndBitwiseExpr or b instanceof AndLogicalExpr) and result = left.booleanAnd(right)) or + ((b instanceof OrBitwiseExpr or b instanceof OrLogicalExpr) and result = left.booleanOr(right)) or + (b instanceof XorBitwiseExpr and result = left.booleanXor(right)) + ) or + // Handle binary expressions that have `String` operands and a boolean result. + exists(BinaryExpr b, string left, string right | + b = this and + left = b.getLeftOperand().(CompileTimeConstantExpr).getStringValue() and + right = b.getRightOperand().(CompileTimeConstantExpr).getStringValue() + | + /* + * JLS 15.28 specifies that compile-time `String` constants are interned. Therefore `==` + * equality can be interpreted as equality over the constant values, not the references. + */ + (b instanceof EQExpr and if left = right then result = true else result = false) or + (b instanceof NEExpr and if left != right then result = true else result = false) + ) or + // Note: no `getFloatValue()`, so we cannot support binary expressions with float or double operands. + // Ternary expressions, where the `true` and `false` expressions are boolean compile-time constants. + exists(ConditionalExpr ce, boolean condition | + ce = this and + condition = ce.getCondition().(CompileTimeConstantExpr).getBooleanValue() + | + if condition = true then + result = ce.getTrueExpr().(CompileTimeConstantExpr).getBooleanValue() + else + result = ce.getFalseExpr().(CompileTimeConstantExpr).getBooleanValue() + ) or + // Parenthesized expressions containing a boolean value. + result = this.(ParExpr).getExpr().(CompileTimeConstantExpr).getBooleanValue() or + // Simple or qualified names where the variable is final and the initializer is a constant. + exists(Variable v | this = v.getAnAccess() | + result = v.getInitializer().(CompileTimeConstantExpr).getBooleanValue() + ) + } + + /** + * Gets the integer value of this expression, where possible. + * + * All computations are performed on QL 32-bit `int`s, so no + * truncation is performed in the case of overflow within `byte` or `short`: + * `((byte)127)+((byte)1)` evaluates to 128 rather than to -128. + * + * Note that this does not handle the following cases: + * + * - values of type `long`, + * - `char` literals. + */ + cached + int getIntValue() { + exists(IntegralType t | this.getType() = t | + t.getName().toLowerCase() != "long" + ) + and + ( + exists(string lit | lit = this.(Literal).getValue() | + // `char` literals may get parsed incorrectly, so disallow. + not this instanceof CharacterLiteral and + result = lit.toInt() + ) + or + exists(CastExpr cast, int val | cast = this and val = cast.getExpr().(CompileTimeConstantExpr).getIntValue() | + if cast.getType().hasName("byte") then result = (val + 128).bitAnd(255) - 128 + else if cast.getType().hasName("short") then result = (val + 32768).bitAnd(65535) - 32768 + else result = val + ) or + result = this.(PlusExpr).getExpr().(CompileTimeConstantExpr).getIntValue() + or + result = -(this.(MinusExpr).getExpr().(CompileTimeConstantExpr).getIntValue()) + or + result = this.(BitNotExpr).getExpr().(CompileTimeConstantExpr).getIntValue().bitNot() + // No `int` value for `LogNotExpr`. + or + exists(BinaryExpr b, int v1, int v2 | b = this and + v1 = b.getLeftOperand().(CompileTimeConstantExpr).getIntValue() and + v2 = b.getRightOperand().(CompileTimeConstantExpr).getIntValue() + | + b instanceof MulExpr and result = v1 * v2 + or + b instanceof DivExpr and result = v1 / v2 + or + b instanceof RemExpr and result = v1 % v2 + or + b instanceof AddExpr and result = v1 + v2 + or + b instanceof SubExpr and result = v1 - v2 + or + b instanceof LShiftExpr and result = v1.bitShiftLeft(v2) + or + b instanceof RShiftExpr and result = v1.bitShiftRightSigned(v2) + or + b instanceof URShiftExpr and result = v1.bitShiftRight(v2) + or + b instanceof AndBitwiseExpr and result = v1.bitAnd(v2) + or + b instanceof OrBitwiseExpr and result = v1.bitOr(v2) + or + b instanceof XorBitwiseExpr and result = v1.bitXor(v2) + // No `int` value for `AndLogicalExpr` or `OrLogicalExpr`. + // No `int` value for `LTExpr`, `GTExpr`, `LEExpr`, `GEExpr`, `EQExpr` or `NEExpr`. + ) + or + // Ternary conditional, with compile-time constant condition. + exists(ConditionalExpr ce, boolean condition | + ce = this and + condition = ce.getCondition().(CompileTimeConstantExpr).getBooleanValue() + | + if condition = true then + result = ce.getTrueExpr().(CompileTimeConstantExpr).getIntValue() + else + result = ce.getFalseExpr().(CompileTimeConstantExpr).getIntValue() + ) + or + result = this.(ParExpr).getExpr().(CompileTimeConstantExpr).getIntValue() + or + // If a `Variable` is a `CompileTimeConstantExpr`, its value is its initializer. + exists(Variable v | this = v.getAnAccess() | + result = v.getInitializer().(CompileTimeConstantExpr).getIntValue() + ) + ) + } +} + +/** An expression parent is an element that may have an expression as its child. */ +class ExprParent extends @exprparent, Top { +} + +/** + * An array access. + * + * For example, `a[i++]` is an array access, where + * `a` is the accessed array and `i++` is + * the index expression of the array access. + */ +class ArrayAccess extends Expr,@arrayaccess { + /** Gets the array that is accessed in this array access. */ + Expr getArray() { result.isNthChildOf(this, 0) } + + /** Gets the index expression of this array access. */ + Expr getIndexExpr() { result.isNthChildOf(this, 1) } + + override string toString() { result = "...[...]" } +} + +/** + * An array creation expression. + * + * For example, an expression such as `new String[3][2]` or + * `new String[][] { { "a", "b", "c" } , { "d", "e", "f" } }`. + * + * In both examples, `String` is the type name. In the first + * example, `2` and `3` are the 0th and 1st dimensions, + * respectively. In the second example, + * `{ { "a", "b", "c" } , { "d", "e", "f" } }` is the initializer. + */ +class ArrayCreationExpr extends Expr,@arraycreationexpr { + /** Gets a dimension of this array creation expression. */ + Expr getADimension() { result.getParent() = this and result.getIndex() >= 0 } + + /** Gets the dimension of this array creation expression at the specified (zero-based) position. */ + Expr getDimension(int index) { + result = this.getADimension() and + result.getIndex() = index + } + + /** Gets the initializer of this array creation expression. */ + ArrayInit getInit() { result.isNthChildOf(this, -2) } + + /** + * Gets the size of the first dimension, if it can be statically determined. + */ + int getFirstDimensionSize() { + if exists(getInit()) then + result = count(getInit().getAnInit()) + else + result = getDimension(0).(CompileTimeConstantExpr).getIntValue() + } + + /** Gets a printable representation of this expression. */ + override string toString() { result = "new " + this.getType().toString() } +} + +/** An array initializer occurs in an array creation expression. */ +class ArrayInit extends Expr,@arrayinit { + /** + * An expression occurring in this initializer. + * This may either be an initializer itself or an + * expression representing an element of the array, + * depending on the level of nesting. + */ + Expr getAnInit() { result.getParent() = this } + + /** Gets the initializer occurring at the specified (zero-based) position. */ + Expr getInit(int index) { result = this.getAnInit() and result.getIndex() = index } + + /** Gets a printable representation of this expression. */ + override string toString() { result = "{...}" } +} + +/** A common super-class that represents all varieties of assignments. */ +class Assignment extends Expr,@assignment { + /** Gets the destination (left-hand side) of the assignment. */ + Expr getDest() { result.isNthChildOf(this, 0) } + + /** + * Gets the source (right-hand side) of the assignment. + * + * For assignments with an implicit operator such as `x += 23`, + * the left-hand side is also a source. + */ + Expr getSource() { result.isNthChildOf(this, 1) } + + /** Gets the right-hand side of the assignment. */ + Expr getRhs() { result.isNthChildOf(this, 1) } + + /** Gets a printable representation of this expression. */ + override string toString() { result = "...=..." } +} + +/** + * A simple assignment expression using the `=` operator. + * + * For example, `x = 23`. + */ +class AssignExpr extends Assignment,@assignexpr { } + +/** + * A common super-class to represent compound assignments, which include an implicit operator. + * + * For example, the compound assignment `x += 23` + * uses `+` as the implicit operator. + */ +class AssignOp extends Assignment,@assignop { + /** + * Gets a source of the compound assignment, which includes both the right-hand side + * and the left-hand side of the assignment. + */ + override Expr getSource() { result.getParent() = this } + + /** Gets a string representation of the assignment operator of this compound assignment. */ + /*abstract*/ string getOp() { result = "??=" } + + /** Gets a printable representation of this expression. */ + override string toString() { result = "..." + this.getOp() + "..." } +} + +/** A compound assignment expression using the `+=` operator. */ +class AssignAddExpr extends AssignOp,@assignaddexpr { override string getOp() { result = "+=" } } +/** A compound assignment expression using the `-=` operator. */ +class AssignSubExpr extends AssignOp,@assignsubexpr { override string getOp() { result = "-=" } } +/** A compound assignment expression using the `*=` operator. */ +class AssignMulExpr extends AssignOp,@assignmulexpr { override string getOp() { result = "*=" } } +/** A compound assignment expression using the `/=` operator. */ +class AssignDivExpr extends AssignOp,@assigndivexpr { override string getOp() { result = "/=" } } +/** A compound assignment expression using the `%=` operator. */ +class AssignRemExpr extends AssignOp,@assignremexpr { override string getOp() { result = "%=" } } +/** A compound assignment expression using the `&=` operator. */ +class AssignAndExpr extends AssignOp,@assignandexpr { override string getOp() { result = "&=" } } +/** A compound assignment expression using the `|=` operator. */ +class AssignOrExpr extends AssignOp,@assignorexpr { override string getOp() { result = "|=" } } +/** A compound assignment expression using the `^=` operator. */ +class AssignXorExpr extends AssignOp,@assignxorexpr { override string getOp() { result = "^=" } } +/** A compound assignment expression using the `<<=` operator. */ +class AssignLShiftExpr extends AssignOp,@assignlshiftexpr { override string getOp() { result = "<<=" } } +/** A compound assignment expression using the `>>=` operator. */ +class AssignRShiftExpr extends AssignOp,@assignrshiftexpr { override string getOp() { result = ">>=" } } +/** A compound assignment expression using the `>>>=` operator. */ +class AssignURShiftExpr extends AssignOp,@assignurshiftexpr { override string getOp() { result = ">>>=" } } + +/** A common super-class to represent constant literals. */ +class Literal extends Expr,@literal { + /** Gets a string representation of this literal. */ + string getLiteral() { namestrings(result,_,this) } + + /** Gets a string representation of the value of this literal. */ + string getValue() { namestrings(_,result,this) } + + /** Gets a printable representation of this expression. */ + override string toString() { result = this.getLiteral() } + + /** Holds if this literal is a compile-time constant expression (as per JLS v8, section 15.28). */ + override predicate isCompileTimeConstant() { + this.getType() instanceof PrimitiveType or + this.getType() instanceof TypeString + } +} + +/** A boolean literal. Either `true` or `false`. */ +class BooleanLiteral extends Literal,@booleanliteral { + + /** Gets the boolean representation of this literal. */ + boolean getBooleanValue() { + result = true and getLiteral() = "true" or + result = false and getLiteral() = "false" + } +} + +/** An integer literal. For example, `23`. */ +class IntegerLiteral extends Literal,@integerliteral { + + /** Gets the int representation of this literal. */ + int getIntValue() { + result = getValue().toInt() + } +} + +/** A long literal. For example, `23l`. */ +class LongLiteral extends Literal,@longliteral {} + +/** A floating point literal. For example, `4.2f`. */ +class FloatingPointLiteral extends Literal,@floatingpointliteral {} + +/** A double literal. For example, `4.2`. */ +class DoubleLiteral extends Literal,@doubleliteral {} + +/** A character literal. For example, `'\n'`. */ +class CharacterLiteral extends Literal,@characterliteral {} + +/** A string literal. For example, `"hello world"`. */ +class StringLiteral extends Literal,@stringliteral { + + /** + * Gets the literal string without the quotes. + */ + string getRepresentedString() { + result = getValue() + } +} + +/** The null literal, written `null`. */ +class NullLiteral extends Literal,@nullliteral { + override string getLiteral() { result = "null" } + override string getValue() { result = "null" } +} + + +/** A common super-class to represent binary operator expressions. */ +class BinaryExpr extends Expr,@binaryexpr { + /** Gets the operand on the left-hand side of this binary expression. */ + Expr getLeftOperand() { result.isNthChildOf(this, 0) } + + /** Gets the operand on the right-hand side of this binary expression. */ + Expr getRightOperand() { result.isNthChildOf(this, 1) } + + /** Gets an operand (left or right), with any parentheses removed. */ + Expr getAnOperand() { + exists(Expr r | r = this.getLeftOperand() or r = this.getRightOperand() | + result = r.getProperExpr() + ) + } + + /** The operands of this binary expression are `e` and `f`, in either order. */ + predicate hasOperands(Expr e, Expr f) { + exists(int i | i in [0..1] | + e.isNthChildOf(this, i) and + f.isNthChildOf(this, 1-i) + ) + } + + /** Gets a printable representation of this expression. */ + override string toString() { result = "..." + this.getOp() + "..." } + + /** Gets a string representation of the operator of this binary expression. */ + /*abstract*/ string getOp() { result = " ?? " } +} + +/** A binary expression using the `*` operator. */ +class MulExpr extends BinaryExpr,@mulexpr { override string getOp() { result = " * " } } +/** A binary expression using the `/` operator. */ +class DivExpr extends BinaryExpr,@divexpr { override string getOp() { result = " / " } } +/** A binary expression using the `%` operator. */ +class RemExpr extends BinaryExpr,@remexpr { override string getOp() { result = " % " } } +/** A binary expression using the `+` operator. */ +class AddExpr extends BinaryExpr,@addexpr { override string getOp() { result = " + " } } +/** A binary expression using the `-` operator. */ +class SubExpr extends BinaryExpr,@subexpr { override string getOp() { result = " - " } } +/** A binary expression using the `<<` operator. */ +class LShiftExpr extends BinaryExpr,@lshiftexpr { override string getOp() { result = " << " } } +/** A binary expression using the `>>` operator. */ +class RShiftExpr extends BinaryExpr,@rshiftexpr { override string getOp() { result = " >> " } } +/** A binary expression using the `>>>` operator. */ +class URShiftExpr extends BinaryExpr,@urshiftexpr { override string getOp() { result = " >>> " } } +/** A binary expression using the `&` operator. */ +class AndBitwiseExpr extends BinaryExpr,@andbitexpr { override string getOp() { result = " & " } } +/** A binary expression using the `|` operator. */ +class OrBitwiseExpr extends BinaryExpr,@orbitexpr { override string getOp() { result = " | " } } +/** A binary expression using the `^` operator. */ +class XorBitwiseExpr extends BinaryExpr,@xorbitexpr { override string getOp() { result = " ^ " } } +/** A binary expression using the `&&` operator. */ +class AndLogicalExpr extends BinaryExpr,@andlogicalexpr { override string getOp() { result = " && " } } +/** A binary expression using the `||` operator. */ +class OrLogicalExpr extends BinaryExpr,@orlogicalexpr { override string getOp() { result = " || " } } +/** A binary expression using the `<` operator. */ +class LTExpr extends BinaryExpr,@ltexpr { override string getOp() { result = " < " } } +/** A binary expression using the `>` operator. */ +class GTExpr extends BinaryExpr,@gtexpr { override string getOp() { result = " > " } } +/** A binary expression using the `<=` operator. */ +class LEExpr extends BinaryExpr,@leexpr { override string getOp() { result = " <= " } } +/** A binary expression using the `>=` operator. */ +class GEExpr extends BinaryExpr,@geexpr { override string getOp() { result = " >= " } } +/** A binary expression using the `==` operator. */ +class EQExpr extends BinaryExpr,@eqexpr { override string getOp() { result = " == " } } +/** A binary expression using the `!=` operator. */ +class NEExpr extends BinaryExpr,@neexpr { override string getOp() { result = " != " } } + +/** + * A bitwise expression. + * + * This includes expressions involving the operators + * `&`, `|`, `^`, or `~`, + * possibly parenthesized. + */ +class BitwiseExpr extends Expr { + BitwiseExpr() { + exists(Expr proper | proper = this.getProperExpr() | + proper instanceof AndBitwiseExpr or + proper instanceof OrBitwiseExpr or + proper instanceof XorBitwiseExpr or + proper instanceof BitNotExpr + ) + } +} + +/** A logical expression. + * + * This includes expressions involving the operators + * `&&`, `||`, or `!`. + */ +class LogicExpr extends Expr { + LogicExpr() { + this instanceof AndLogicalExpr or + this instanceof OrLogicalExpr or + this instanceof LogNotExpr + } + + /** Gets an operand of this logical expression. */ + Expr getAnOperand() { + this.(BinaryExpr).getAnOperand() = result or + this.(UnaryExpr).getExpr() = result + } +} + +/** + * A comparison expression. + * + * This includes expressions using the operators + * `<=`, `>=`, `<` or `>`. + */ +abstract class ComparisonExpr extends BinaryExpr { + /** + * DEPRECATED: use `getLesserOperand()` instead. + */ + deprecated + Expr getLesser() { + result = getLesserOperand() + } + + /** + * DEPRECATED: use `getGreaterOperand()` instead. + */ + deprecated + Expr getGreater() { + result = getGreaterOperand() + } + + /** + * Gets the lesser operand of this comparison expression. + * + * For example, `x` is the lesser operand + * in `x < 0`, and `0` is the + * lesser operand in `x > 0`. + */ + abstract Expr getLesserOperand(); + + /** + * Gets the greater operand of this comparison expression. + * + * For example, `x` is the greater operand + * in `x > 0`, and `0` is the + * greater operand in `x < 0`. + */ + abstract Expr getGreaterOperand(); + + /** Holds if this comparison is strict, i.e. `<` or `>`. */ + predicate isStrict() { + this instanceof LTExpr or this instanceof GTExpr + } +} + +/** A comparison expression using the operator `<` or `<=`. */ +class LessThanComparison extends ComparisonExpr { + LessThanComparison() { + this instanceof LTExpr or this instanceof LEExpr + } + + /** Gets the lesser operand of this comparison expression. */ + override Expr getLesserOperand() { + result = this.getLeftOperand() + } + + /** Gets the greater operand of this comparison expression. */ + override Expr getGreaterOperand() { + result = this.getRightOperand() + } +} + +/** A comparison expression using the operator `>` or `>=`. */ +class GreaterThanComparison extends ComparisonExpr { + GreaterThanComparison() { + this instanceof GTExpr or this instanceof GEExpr + } + + /** Gets the lesser operand of this comparison expression. */ + override Expr getLesserOperand() { + result = this.getRightOperand() + } + + /** Gets the greater operand of this comparison expression. */ + override Expr getGreaterOperand() { + result = this.getLeftOperand() + } +} + +/** + * An equality test is a binary expression using + * the `==` or `!=` operator. + */ +class EqualityTest extends BinaryExpr { + EqualityTest() { + this instanceof EQExpr or + this instanceof NEExpr + } + + boolean polarity() { + result = true and this instanceof EQExpr + or + result = false and this instanceof NEExpr + } +} + +/** A common super-class that represents unary operator expressions. */ +class UnaryExpr extends Expr,@unaryexpr { + /** Gets the operand expression. */ + Expr getExpr() { result.getParent() = this } +} + +/** + * A unary assignment expression is a unary expression using the + * prefix or postfix `++` or `--` operator. + */ +class UnaryAssignExpr extends UnaryExpr,@unaryassignment { +} + +/** A post-increment expression. For example, `i++`. */ +class PostIncExpr extends UnaryAssignExpr,@postincexpr { override string toString() { result = "...++" } } +/** A post-decrement expression. For example, `i--`. */ +class PostDecExpr extends UnaryAssignExpr,@postdecexpr { override string toString() { result = "...--" } } +/** A pre-increment expression. For example, `++i`. */ +class PreIncExpr extends UnaryAssignExpr,@preincexpr { override string toString() { result = "++..." } } +/** A pre-decrement expression. For example, `--i`. */ +class PreDecExpr extends UnaryAssignExpr,@predecexpr { override string toString() { result = "--..." } } +/** A unary minus expression. For example, `-i`. */ +class MinusExpr extends UnaryExpr,@minusexpr { override string toString() { result = "-..." } } +/** A unary plus expression. For example, `+i`. */ +class PlusExpr extends UnaryExpr,@plusexpr { override string toString() { result = "+..." } } +/** A bit negation expression. For example, `~x`. */ +class BitNotExpr extends UnaryExpr,@bitnotexpr { override string toString() { result = "~..." } } +/** A logical negation expression. For example, `!b`. */ +class LogNotExpr extends UnaryExpr,@lognotexpr { override string toString() { result = "!..." } } + +/** A cast expression. */ +class CastExpr extends Expr,@castexpr { + /** Gets the target type of this cast expression. */ + Expr getTypeExpr() { result.isNthChildOf(this, 0) } + + /** Gets the expression to which the cast operator is applied. */ + Expr getExpr() { result.isNthChildOf(this, 1) } + + /** Gets a printable representation of this expression. */ + override string toString() { result = "(...)..." } +} + +/** A class instance creation expression. */ +class ClassInstanceExpr extends Expr, ConstructorCall, @classinstancexpr { + /** Gets the number of arguments provided to the constructor of the class instance creation expression. */ + override int getNumArgument() { count(this.getAnArgument()) = result } + + /** Gets an argument provided to the constructor of this class instance creation expression. */ + override Expr getAnArgument() { result.getIndex() >= 0 and result.getParent() = this } + + /** + * Gets the argument provided to the constructor of this class instance creation expression + * at the specified (zero-based) position. + */ + override Expr getArgument(int index) { + result.getIndex() = index and + result = this.getAnArgument() + } + + /** + * Gets a type argument provided to the constructor of this class instance creation expression. + * + * This is used for instantiations of parameterized classes. + */ + Expr getATypeArgument() { result = this.getTypeName().(TypeAccess).getATypeArgument() } + + /** + * Gets the type argument provided to the constructor of this class instance creation expression + * at the specified (zero-based) position. + */ + Expr getTypeArgument(int index) { result = this.getTypeName().(TypeAccess).getTypeArgument(index) } + + /** Gets the qualifier of this class instance creation expression, if any. */ + override Expr getQualifier() { result.isNthChildOf(this, -2) } + + /** + * Gets the access to the type that is instantiated or subclassed by this + * class instance creation expression. + */ + Expr getTypeName() { result.isNthChildOf(this, -3) } + + /** Gets the constructor invoked by this class instance creation expression. */ + override Constructor getConstructor() { callableBinding(this,result) } + + /** Gets the anonymous class created by this class instance creation expression, if any. */ + AnonymousClass getAnonymousClass() { isAnonymClass(result, this) } + + /** + * Holds if this class instance creation expression has an + * empty type argument list of the form `<>`. + */ + predicate isDiamond() { + this.getType() instanceof ParameterizedClass and + not exists(this.getATypeArgument()) + } + + /** Gets the immediately enclosing callable of this class instance creation expression. */ + override Callable getEnclosingCallable() { result = Expr.super.getEnclosingCallable() } + + /** Gets the immediately enclosing statement of this class instance creation expression. */ + override Stmt getEnclosingStmt() { result = Expr.super.getEnclosingStmt() } + + /** Gets a printable representation of this expression. */ + override string toString() { result = "new " + this.getConstructor().getName() + "(...)" } +} + +/** A functional expression is either a lambda expression or a member reference expression. */ +abstract class FunctionalExpr extends ClassInstanceExpr { + /** Gets the implicit method corresponding to this functional expression. */ + abstract Method asMethod(); +} + +/** + * Lambda expressions are represented by their implicit class instance creation expressions, + * which instantiate an anonymous class that overrides the unique method designated by + * their functional interface type. The parameters of the lambda expression correspond + * to the parameters of the overriding method, and the lambda body corresponds to the + * body of the overriding method (enclosed by a return statement and a block in the case + * of lambda expressions whose body is an expression). + * + * For details, see JLS v8 section 15.27.4: Run-Time Evaluation of Lambda Expressions. + */ +class LambdaExpr extends FunctionalExpr, @lambdaexpr { + /** + * Gets the implicit method corresponding to this lambda expression. + * The parameters of the lambda expression are the parameters of this method. + */ + override Method asMethod() { result = getAnonymousClass().getAMethod() } + + /** Holds if the body of this lambda is an expression. */ + predicate hasExprBody() { lambdaKind(this,0) } + /** Holds if the body of this lambda is a statement. */ + predicate hasStmtBody() { lambdaKind(this,1) } + + /** Gets the body of this lambda expression, if it is an expression. */ + Expr getExprBody() { hasExprBody() and result = asMethod().getBody().getAChild().(ReturnStmt).getResult() } + /** Gets the body of this lambda expression, if it is a statement. */ + Stmt getStmtBody() { hasStmtBody() and result = asMethod().getBody() } + + /** Gets a printable representation of this expression. */ + override string toString() { result = "...->..." } +} + +/** + * Member references are represented by their implicit class instance expressions, + * which instantiate an anonymous class that overrides the unique method designated by + * their functional interface type. The correspondence of the parameters of the overriding + * method in the anonymous class with the parameters of the callable that is referenced + * differs depending on the particular kind of member reference expression. + * + * For details, see JLS v8 section 15.13.3: Run-Time Evaluation of Method References. + */ +class MemberRefExpr extends FunctionalExpr, @memberref { + /** + * Gets the implicit method corresponding to this member reference expression. + * The body of this method is a return statement (enclosed in a block) whose expression + * is either a method access (if the reference is to a method), a class instance creation expression + * (if the reference is to a constructor) or an array creation expression (if the reference + * is to an array constructor). + */ + override Method asMethod() { result = getAnonymousClass().getAMethod() } + /** + * Gets the method or constructor referenced by this member reference expression. + */ + Callable getReferencedCallable() { memberRefBinding(this,result) } + + /** Gets a printable representation of this expression. */ + override string toString() { result = "...::..." } +} + +/** + * A conditional expression of the form `a ? b : c`, where `a` is the condition, + * `b` is the expression that is evaluated if the condition evaluates to `true`, + * and `c` is the expression that is evaluated if the condition evaluates to `false`. + */ +class ConditionalExpr extends Expr,@conditionalexpr { + /** Gets the condition of this conditional expression. */ + Expr getCondition() { result.isNthChildOf(this, 0) } + + /** + * Gets the expression that is evaluated if the condition of this + * conditional expression evaluates to `true`. + */ + Expr getTrueExpr() { result.isNthChildOf(this, 1) } + + /** + * Gets the expression that is evaluated if the condition of this + * conditional expression evaluates to `false`. + */ + Expr getFalseExpr() { result.isNthChildOf(this, 2) } + + /** Gets a printable representation of this expression. */ + override string toString() { result = "...?...:..." } +} + +/** A parenthesised expression. */ +class ParExpr extends Expr,@parexpr { + /** Gets the expression inside the parentheses. */ + Expr getExpr() { result.getParent() = this } + + /** Gets a printable representation of this expression. */ + override string toString() { result = "(...)" } +} + +/** An `instanceof` expression. */ +class InstanceOfExpr extends Expr,@instanceofexpr { + /** Gets the expression on the left-hand side of the `instanceof` operator. */ + Expr getExpr() { result.isNthChildOf(this, 0) } + + /** Gets the access to the type on the right-hand side of the `instanceof` operator. */ + Expr getTypeName() { result.isNthChildOf(this, 1) } + + /** Gets a printable representation of this expression. */ + override string toString() { result = "...instanceof..." } +} + +/** + * A local variable declaration expression. + * + * Contexts in which such expressions may occur include + * local variable declaration statements and `for` loops. + */ +class LocalVariableDeclExpr extends Expr,@localvariabledeclexpr { + /** Gets an access to the variable declared by this local variable declaration expression. */ + VarAccess getAnAccess() { variableBinding(result,this.getVariable()) } + + /** Gets the local variable declared by this local variable declaration expression. */ + LocalVariableDecl getVariable() { localvars(result,_,_,this) } + + /** Gets the type access of this local variable declaration expression. */ + Expr getTypeAccess() { + exists(LocalVariableDeclStmt lvds | lvds.getAVariable() = this | result.isNthChildOf(lvds, 0)) or + exists(CatchClause cc | cc.getVariable() = this | result.isNthChildOf(cc, -1)) or + exists(ForStmt fs | fs.getAnInit() = this | result.isNthChildOf(fs, 0)) or + exists(EnhancedForStmt efs | efs.getVariable() = this | result.isNthChildOf(efs, -1)) + } + + /** Gets the name of the variable declared by this local variable declaration expression. */ + string getName() { result = this.getVariable().getName() } + + /** Gets the initializer expression of this local variable declaration expression, if any. */ + Expr getInit() { result.isNthChildOf(this, 0) } + + /** Holds if this variable declaration implicitly initializes the variable. */ + predicate hasImplicitInit() { + exists(CatchClause cc | cc.getVariable() = this) or + exists(EnhancedForStmt efs | efs.getVariable() = this) + } + + /** Gets a printable representation of this expression. */ + override string toString() { result = this.getName() } +} + +/** An update of a variable or an initialization of the variable. */ +class VariableUpdate extends Expr { + VariableUpdate() { + this.(Assignment).getDest() instanceof VarAccess or + this instanceof LocalVariableDeclExpr or + this.(UnaryAssignExpr).getExpr() instanceof VarAccess + } + + /** Gets the destination of this variable update. */ + Variable getDestVar() { + result.getAnAccess() = this.(Assignment).getDest() or + result = this.(LocalVariableDeclExpr).getVariable() or + result.getAnAccess() = this.(UnaryAssignExpr).getExpr() + } +} + +/** + * An assignment to a variable or an initialization of the variable. + */ +class VariableAssign extends VariableUpdate { + VariableAssign() { + this instanceof AssignExpr or + this instanceof LocalVariableDeclExpr + } + + /** + * Gets the source of this assignment, if any. + * + * An initialization in a `CatchClause` or `EnhancedForStmt` is implicit and + * does not have a source. + */ + Expr getSource() { + result = this.(AssignExpr).getSource() or + result = this.(LocalVariableDeclExpr).getInit() + } +} + +/** A type literal. For example, `String.class`. */ +class TypeLiteral extends Expr,@typeliteral { + /** Gets the access to the type whose class is accessed. */ + Expr getTypeName() { result.getParent() = this } + + /** Gets a printable representation of this expression. */ + override string toString() { result = this.getTypeName().toString() + ".class" } +} + +/** + * A use of one of the keywords `this` or `super`, which may be qualified. + */ +abstract class InstanceAccess extends Expr { + /** + * Gets the qualifying expression, if any. + * + * For example, the qualifying expression of `A.this` is `A`. + */ + Expr getQualifier() { result.getParent() = this } + + /** + * Holds if this instance access gets the value of `this`. That is, it is not + * an enclosing instance. + * This never holds for accesses in lambda expressions as they cannot access + * their own instance directly. + */ + predicate isOwnInstanceAccess() { + not isEnclosingInstanceAccess(_) + } + + /** Holds if this instance access is to an enclosing instance of type `t`. */ + predicate isEnclosingInstanceAccess(RefType t) { + t = getQualifier().getType().(RefType).getSourceDeclaration() and + t != getEnclosingCallable().getDeclaringType() + or + not exists(getQualifier()) and + exists(LambdaExpr lam | lam.asMethod() = getEnclosingCallable() | + t = lam.getAnonymousClass().getEnclosingType() + ) + } +} + +/** + * A use of the keyword `this`, which may be qualified. + * + * Such an expression allows access to an enclosing instance. + * For example, `A.this` refers to the enclosing instance + * of type `A`. + */ +class ThisAccess extends InstanceAccess,@thisaccess { + /** Gets a printable representation of this expression. */ + override string toString() { + if exists(this.getQualifier()) then ( + result = this.getQualifier() + ".this" + ) else ( + result = "this" + ) + } +} + +/** + * A use of the keyword `super`, which may be qualified. + * + * Such an expression allows access to super-class members of an enclosing instance. + * For example, `A.super.x`. + */ +class SuperAccess extends InstanceAccess,@superaccess { + /** Gets a printable representation of this expression. */ + override string toString() { + if exists(this.getQualifier()) then ( + result = this.getQualifier() + ".super" + ) else ( + result = "super" + ) + } +} + +/** + * A variable access is a (possibly qualified) reference to + * a field, parameter or local variable. + */ +class VarAccess extends Expr,@varaccess { + /** Gets the qualifier of this variable access, if any. */ + Expr getQualifier() { result.getParent() = this } + + /** Holds if this variable access has a qualifier. */ + predicate hasQualifier() { exists(getQualifier()) } + + /** Gets the variable accessed by this variable access. */ + Variable getVariable() { variableBinding(this,result) } + + /** + * Holds if this variable access is an l-value. + * + * An l-value is a write access to a variable, which occurs as the destination of an assignment. + */ + predicate isLValue() { + exists(Assignment a | a.getDest() = this) or + exists(PreIncExpr e | e.getExpr() = this) or + exists(PreDecExpr e | e.getExpr() = this) or + exists(PostIncExpr e | e.getExpr() = this) or + exists(PostDecExpr e | e.getExpr() = this) + } + + /** + * Holds if this variable access is an r-value. + * + * An r-value is a read access to a variable. + * In other words, it is a variable access that does _not_ occur as the destination of + * a simple assignment, but it may occur as the destination of a compound assignment + * or a unary assignment. + */ + predicate isRValue() { + not exists(AssignExpr a | a.getDest() = this) + } + + /** Gets a printable representation of this expression. */ + override string toString() { + result = this.getQualifier().toString() + "." + this.getVariable().getName() or + (not this.hasQualifier() and result = this.getVariable().getName()) + } + + /** + * Holds if this access refers to a local variable or a field of + * the receiver of the enclosing method or constructor. + */ + predicate isLocal() { + // The access has no qualifier, or... + not hasQualifier() or + // the qualifier is either `this` or `A.this`, where `A` is the enclosing type, or + // the qualifier is either `super` or `A.super`, where `A` is the enclosing type. + getQualifier().(InstanceAccess).isOwnInstanceAccess() + } +} + +/** + * An l-value is a write access to a variable, which occurs as the destination of an assignment. + */ +class LValue extends VarAccess { + LValue() { this.isLValue() } + + /** + * Gets a source expression used in an assignment to this l-value. + * + * For assignments using the `=` operator, the source expression + * is simply the RHS of the assignment. + * + * Note that for l-values occurring on the LHS of compound assignment operators + * (such as (`+=`), both the RHS and the LHS of the compound assignment + * are source expressions of the assignment. + */ + Expr getRHS() { + exists(Assignment e | e.getDest() = this and e.getSource() = result) + } +} + +/** + * An r-value is a read access to a variable. + * + * In other words, it is a variable access that does _not_ occur as the destination of + * a simple assignment, but it may occur as the destination of a compound assignment + * or a unary assignment. + */ +class RValue extends VarAccess { + RValue() { this.isRValue() } +} + +/** A method access is an invocation of a method with a list of arguments. */ +class MethodAccess extends Expr, Call, @methodaccess { + /** Gets the qualifying expression of this method access, if any. */ + override Expr getQualifier() { result.isNthChildOf(this, -1) } + + /** Holds if this method access has a qualifier. */ + predicate hasQualifier() { exists(getQualifier()) } + + /** Gets an argument supplied to the method that is invoked using this method access. */ + override Expr getAnArgument() { result.getIndex() >= 0 and result.getParent() = this } + + /** Gets the argument at the specified (zero-based) position in this method access. */ + override Expr getArgument(int index) { exprs(result, _, _, this, index) and index >= 0 } + + /** Gets a type argument supplied as part of this method access, if any. */ + Expr getATypeArgument() { result.getIndex() <= -2 and result.getParent() = this } + + /** Gets the type argument at the specified (zero-based) position in this method access, if any. */ + Expr getTypeArgument(int index) { + result = this.getATypeArgument() and + (-2 - result.getIndex()) = index + } + + /** Gets the method accessed by this method access. */ + Method getMethod() { callableBinding(this,result) } + + /** Gets the immediately enclosing callable that contains this method access. */ + override Callable getEnclosingCallable() { result = Expr.super.getEnclosingCallable() } + + /** Gets the immediately enclosing statement that contains this method access. */ + override Stmt getEnclosingStmt() { result = Expr.super.getEnclosingStmt() } + + /** Gets a printable representation of this expression. */ + override string toString() { result = this.printAccess() } + + /** Gets a printable representation of this expression. */ + string printAccess() { + result = this.getMethod().getName() + "(...)" + } + + /** + * Gets the type of the qualifier on which this method is invoked, or + * the enclosing type if there is no qualifier. + */ + RefType getReceiverType() { + result = getQualifier().getType() or + not hasQualifier() and result = getEnclosingCallable().getDeclaringType() + } + + /** + * Holds if this is a method access to an instance method of `this`. That is, + * the qualifier is either an explicit or implicit unqualified `this` or `super`. + */ + predicate isOwnMethodAccess() { + Qualifier::ownMemberAccess(this) + } + + /** + * Holds if this is a method access to an instance method of the enclosing + * class `t`. That is, the qualifier is either an explicit or implicit + * `t`-qualified `this` or `super`. + */ + predicate isEnclosingMethodAccess(RefType t) { + Qualifier::enclosingMemberAccess(this, t) + } +} + +/** A type access is a (possibly qualified) reference to a type. */ +class TypeAccess extends Expr, Annotatable, @typeaccess { + /** Gets the qualifier of this type access, if any. */ + Expr getQualifier() { result.isNthChildOf(this, -1) } + + /** Holds if this type access has a qualifier. */ + predicate hasQualifier() { exists(Expr e | e = this.getQualifier()) } + + /** Gets a type argument supplied to this type access. */ + Expr getATypeArgument() { result.getIndex() >= 0 and result.getParent() = this } + + /** Gets the type argument at the specified (zero-based) position in this type access. */ + Expr getTypeArgument(int index) { + result = this.getATypeArgument() and + result.getIndex() = index + } + + /** Holds if this type access has a type argument. */ + predicate hasTypeArgument() { exists(Expr e | e = this.getATypeArgument()) } + + /** Gets the compilation unit in which this type access occurs. */ + override CompilationUnit getCompilationUnit() { result = Expr.super.getCompilationUnit() } + + /** Gets a printable representation of this expression. */ + override string toString() { + result = this.getQualifier().toString() + "." + this.getType().toString() or + (not this.hasQualifier() and result = this.getType().toString()) + } +} + +/** An array type access is a type access of the form `String[]`. */ +class ArrayTypeAccess extends Expr,@arraytypeaccess { + /** + * Gets the expression representing the component type of this array type access. + * + * For example, in the array type access `String[][]`, + * the component type is `String[]` and the + * element type is `String`. + */ + Expr getComponentName() { result.getParent() = this } + + /** Gets a printable representation of this expression. */ + override string toString() { result = "...[]" } +} + +/** + * A union type access is a type access of the form `T1 | T2`. + * + * Such a type access can only occur in a multi-catch clause. + */ +class UnionTypeAccess extends Expr, @uniontypeaccess { + /** One of the alternatives in the union type access. */ + Expr getAnAlternative() { result.getParent() = this } + + /** Gets a printable representation of this expression. */ + override string toString() { result = "...|..." } +} + +/** + * An intersection type access expression is a type access + * of the form `T0 & T1 & ... & Tn`, + * where `T0` is a class or interface type and + * `T1, ..., Tn` are interface types. + * + * An intersection type access _expression_ can only + * occur in a cast expression. + * + * Note that intersection types can also occur as the bound + * of a bounded type, such as a type variable. Each component + * of such a bound is represented by the QL class `TypeBound`. + */ +class IntersectionTypeAccess extends Expr, @intersectiontypeaccess { + /** + * One of the bounds of this intersection type access expression. + * + * For example, in the intersection type access expression + * `Runnable & Cloneable`, both `Runnable` + * and `Cloneable` are bounds. + */ + Expr getABound() { result.getParent() = this } + /** + * Gets the bound at a specified (zero-based) position in this intersection type access expression. + * + * For example, in the intersection type access expression + * `Runnable & Cloneable`, the bound at position 0 is + * `Runnable` and the bound at position 1 is `Cloneable`. + */ + Expr getBound(int index) { result.isNthChildOf(this, index) } + + /** Gets a printable representation of this expression. */ + override string toString() { result = "...&..." } +} + +/** A package access. */ +class PackageAccess extends Expr,@packageaccess { + /** Gets a printable representation of this expression. */ + override string toString() { result = "package" } +} + +/** A wildcard type access, which may have either a lower or an upper bound. */ +class WildcardTypeAccess extends Expr,@wildcardtypeaccess { + /** Gets the upper bound of this wildcard type access, if any. */ + Expr getUpperBound() { result.isNthChildOf(this, 0) } + + /** Gets the lower bound of this wildcard type access, if any. */ + Expr getLowerBound() { result.isNthChildOf(this, 1) } + + /** Holds if this wildcard is not bounded by any type bounds. */ + predicate hasNoBound() { not exists(TypeAccess t | t.getParent() = this) } + + /** Gets a printable representation of this expression. */ + override string toString() { result = "? ..." } +} + +/** + * Any call to a callable. + * + * This includes method calls, constructor and super constructor invocations, + * and constructors invoked through class instantiation. + */ +class Call extends Top, @caller { + /** Gets an argument supplied in this call. */ + /*abstract*/ Expr getAnArgument() { none() } + /** Gets the argument specified at the (zero-based) position in this call. */ + /*abstract*/ Expr getArgument(int n) { none() } + /** Gets the immediately enclosing callable that contains this call. */ + /*abstract*/ Callable getEnclosingCallable() { none() } + /** Gets the qualifying expression of this call, if any. */ + /*abstract*/ Expr getQualifier() { none() } + /** Gets the enclosing statement of this call. */ + /*abstract*/ Stmt getEnclosingStmt() { none() } + + /** Gets the number of arguments provided in this call. */ + int getNumArgument() { count(this.getAnArgument()) = result } + + /** Gets the target callable of this call. */ + Callable getCallee() { + callableBinding(this,result) + } + + /** Gets the callable invoking this call. */ + Callable getCaller() { + result = getEnclosingCallable() + } +} + +/** A polymorphic call to an instance method. */ +class VirtualMethodAccess extends MethodAccess { + VirtualMethodAccess() { + this.getMethod().isVirtual() and + not this.getQualifier() instanceof SuperAccess + } +} + +/** A static method call. */ +class StaticMethodAccess extends MethodAccess { + StaticMethodAccess() { + this.getMethod().isStatic() + } +} + +/** A call to a method in the superclass. */ +class SuperMethodAccess extends MethodAccess { + SuperMethodAccess() { + this.getQualifier() instanceof SuperAccess + } +} + +/** + * A constructor call, which occurs either as a constructor invocation inside a + * constructor, or as part of a class instance expression. + */ +abstract class ConstructorCall extends Call { + + /** Gets the target constructor of the class being instantiated. */ + abstract Constructor getConstructor(); + + /** Holds if this constructor call is an explicit call to `this(...)`. */ + predicate callsThis() { + this instanceof ThisConstructorInvocationStmt + } + + /** Holds if this constructor call is an explicit call to `super(...)`. */ + predicate callsSuper() { + this instanceof SuperConstructorInvocationStmt + } + + /** Gets the type of the object instantiated by this constructor call. */ + RefType getConstructedType() { result = this.getConstructor().getDeclaringType() } +} + +/** An expression that accesses a field. */ +class FieldAccess extends VarAccess { + FieldAccess() { + this.getVariable() instanceof Field + } + + /** Gets the field accessed by this field access expression. */ + Field getField() { + this.getVariable() = result + } + + /** Gets the immediately enclosing callable that contains this field access expression. */ + Callable getSite() { + this.getEnclosingCallable() = result + } + + /** + * Holds if this is a field access to an instance field of `this`. That is, + * the qualifier is either an explicit or implicit unqualified `this` or `super`. + */ + predicate isOwnFieldAccess() { + Qualifier::ownMemberAccess(this) + } + + /** + * Holds if this is a field access to an instance field of the enclosing + * class `t`. That is, the qualifier is either an explicit or implicit + * `t`-qualified `this` or `super`. + */ + predicate isEnclosingFieldAccess(RefType t) { + Qualifier::enclosingMemberAccess(this, t) + } +} + +private module Qualifier { + /** A type qualifier for an `InstanceAccess`. */ + private newtype TThisQualifier = TThis() or TEnclosing(RefType t) + + /** An expression that accesses a member. That is, either a `FieldAccess` or a `MethodAccess`. */ + class MemberAccess extends Expr { + MemberAccess() { + this instanceof FieldAccess or + this instanceof MethodAccess + } + /** Gets the member accessed by this member access. */ + Member getMember() { + result = this.(FieldAccess).getField() or + result = this.(MethodAccess).getMethod() + } + /** Gets the qualifier of this member access, if any. */ + Expr getQualifier() { + result = this.(FieldAccess).getQualifier() or + result = this.(MethodAccess).getQualifier() + } + } + + /** + * Gets the implicit type qualifier of the implicit `ThisAccess` qualifier of + * an access to `m` from within `ic`, which does not itself inherit `m`. + */ + private RefType getImplicitEnclosingQualifier(InnerClass ic, Member m) { + exists(RefType enclosing | enclosing = ic.getEnclosingType() | + if enclosing.inherits(m) then + result = enclosing + else + result = getImplicitEnclosingQualifier(enclosing, m) + ) + } + + /** + * Gets the implicit type qualifier of the implicit `ThisAccess` qualifier of `ma`. + */ + private TThisQualifier getImplicitQualifier(MemberAccess ma) { + exists(Member m | m = ma.getMember() | + not m.isStatic() and + not exists(ma.getQualifier()) and + exists(RefType t | t = ma.getEnclosingCallable().getDeclaringType() | + not t instanceof InnerClass and result = TThis() or + exists(InnerClass ic | ic = t | + if ic.inherits(m) then + result = TThis() + else + result = TEnclosing(getImplicitEnclosingQualifier(ic, m)) + ) + ) + ) + } + + /** + * Gets the type qualifier of the `InstanceAccess` qualifier of `ma`. + */ + private TThisQualifier getThisQualifier(MemberAccess ma) { + result = getImplicitQualifier(ma) or + exists(Expr q | + not ma.getMember().isStatic() and + q = ma.getQualifier() + | + exists(InstanceAccess ia | ia = q and ia.isOwnInstanceAccess() and result = TThis()) or + exists(InstanceAccess ia, RefType qt | ia = q and ia.isEnclosingInstanceAccess(qt) and result = TEnclosing(qt)) + ) + } + + /** + * Holds if `ma` is a member access to an instance field or method of `this`. That is, + * the qualifier is either an explicit or implicit unqualified `this` or `super`. + */ + predicate ownMemberAccess(MemberAccess ma) { + TThis() = getThisQualifier(ma) + } + + /** + * Holds if `ma` is a member access to an instance field or method of the enclosing + * class `t`. That is, the qualifier is either an explicit or implicit + * `t`-qualified `this` or `super`. + */ + predicate enclosingMemberAccess(MemberAccess ma, RefType t) { + TEnclosing(t) = getThisQualifier(ma) + } +} + +/** An expression that assigns a value to a field. */ +class FieldWrite extends FieldAccess { + FieldWrite() { + exists(Field f | f = getVariable() and isLValue()) + } +} + +/** An expression that reads a field. */ +class FieldRead extends FieldAccess { + FieldRead() { + exists(Field f | f = getVariable() and isRValue()) + } +} + +private predicate hasInstantiation(RefType t) { + t instanceof TypeVariable or + t instanceof Wildcard or + hasInstantiation(t.(Array).getComponentType()) or + hasInstantiation(t.(ParameterizedType).getATypeArgument()) +} + +/** An argument to a call. */ +class Argument extends Expr { + Call call; + int pos; + + Argument() { + call.getArgument(pos) = this + } + + /** Gets the call that has this argument. */ + Call getCall() { result = call } + + /** Gets the position of this argument. */ + int getPosition() { + result = pos + } + + /** + * Holds if this argument is an array of the appropriate type passed to a + * varargs parameter. + */ + predicate isExplicitVarargsArray() { + exists(Array typ, Parameter p, Type ptyp | + typ = this.getType() and + pos = call.getNumArgument() - 1 and + call.getCallee().getParameter(pos) = p and + p.isVarargs() and + ptyp = p.getType() and + ( + hasSubtype*(ptyp, typ) or + // If the types don't match then we'll guess based on whether there are type variables involved. + hasInstantiation(ptyp.(Array).getComponentType()) + ) + ) + } + + /** Holds if this argument is part of an implicit varargs array. */ + predicate isVararg() { + isNthVararg(_) + } + + /** + * Holds if this argument is part of an implicit varargs array at the + * given array index. + */ + predicate isNthVararg(int arrayindex) { + not isExplicitVarargsArray() and + exists(Callable tgt | + call.getCallee() = tgt and + tgt.isVarargs() and + arrayindex = pos - tgt.getNumberOfParameters() + 1 and + arrayindex >= 0 + ) + } +} diff --git a/java/ql/src/semmle/code/java/GeneratedFiles.qll b/java/ql/src/semmle/code/java/GeneratedFiles.qll new file mode 100644 index 00000000000..bd49af52c8b --- /dev/null +++ b/java/ql/src/semmle/code/java/GeneratedFiles.qll @@ -0,0 +1,70 @@ +/** + * Provides classes and predicates for working with the most common types of generated files. + */ + +import Type +private import semmle.code.java.frameworks.JavaxAnnotations + +/** A Java class that is detected as having been generated. */ +abstract class GeneratedClass extends Class { + +} + +/** + * A Java class annotated with a `@Generated` annotation. + */ +class AnnotatedGeneratedClass extends GeneratedClass { + AnnotatedGeneratedClass() { + this.getAnAnnotation() instanceof GeneratedAnnotation + } +} + +/** A Java class generated by an ANTLR scanner or parser class. */ +class AntlrGenerated extends GeneratedClass { + AntlrGenerated() { + exists(RefType t | this.getASupertype+() = t | + // ANTLR v3 + t.hasQualifiedName("org.antlr.runtime", "Lexer") or + t.hasQualifiedName("org.antlr.runtime", "Parser") or + t.hasQualifiedName("org.antlr.runtime.tree", "TreeParser") or + // ANTLR v2 + t.hasQualifiedName("antlr","TreeParser") or + t.hasQualifiedName("antlr","CharScanner") or + t.hasQualifiedName("antlr","LLkParser") + ) + } +} + +/** A generated callable is a callable declared in a generated class. */ +class GeneratedCallable extends Callable { + GeneratedCallable() { + this.getDeclaringType() instanceof GeneratedClass + } +} + +/** + * A file that is detected as having been generated. + */ +abstract class GeneratedFile extends File { +} + +/** + * A file detected as generated based on commonly-used marker comments. + */ +library +class MarkerCommentGeneratedFile extends GeneratedFile { + MarkerCommentGeneratedFile() { + exists(JavadocElement t | t.getFile() = this | + exists(string msg | msg = t.getText() | + msg.regexpMatch("(?i).*\\bGenerated By\\b.*\\bDo not edit\\b.*") or + msg.regexpMatch("(?i).*\\bThis (file|class|interface|art[ei]fact) (was|is|(has been)) (?:auto[ -]?)?gener(e?)ated.*") or + msg.regexpMatch("(?i).*\\bAny modifications to this file will be lost\\b.*") or + msg.regexpMatch("(?i).*\\bThis (file|class|interface|art[ei]fact) (was|is) (?:mechanically|automatically) generated\\b.*") or + msg.regexpMatch("(?i).*\\bThe following code was (?:auto[ -]?)?generated (?:by|from)\\b.*") or + msg.regexpMatch("(?i).*\\bAutogenerated by Thrift.*") or + msg.regexpMatch("(?i).*\\bGenerated By.*JavaCC.*") or + msg.regexpMatch("(?i).*\\bGenerated from .* by ANTLR.*") + ) + ) + } +} diff --git a/java/ql/src/semmle/code/java/Generics.qll b/java/ql/src/semmle/code/java/Generics.qll new file mode 100755 index 00000000000..73b08d9de62 --- /dev/null +++ b/java/ql/src/semmle/code/java/Generics.qll @@ -0,0 +1,565 @@ +/** + * Provides classes and predicates for working with generic types. + * + * A generic type as declared in the program, for example + * + * ``` + * class X { } + * ``` + * is represented by a `GenericType`. + * + * A parameterized instance of such a type, for example + * + * ``` + * X + * ``` + * is represented by a `ParameterizedType`. + * + * For dealing with legacy code that is unaware of generics, every generic type has a + * "raw" version, represented by a `RawType`. In the example, `X` is the raw version of + * `X`. + * + * The erasure of a parameterized or raw type is its generic counterpart. + * + * Type parameters may have bounds as in + * + * ``` + * class X { } + * ``` + * which are represented by a `TypeBound`. + * + * The terminology for generic methods is analogous. + */ + +import Type + +/** + * A generic type is a type that has a type parameter. + * + * For example, `X` in `class X { }`. + */ +class GenericType extends RefType { + GenericType() { typeVars(_,_,_,_,this) } + + /** + * Gets a parameterization of this generic type, where each use of + * a formal type parameter has been replaced by its argument. + * + * For example, `List` is a parameterization of + * the generic type `List`, where `E` is a type parameter. + */ + ParameterizedType getAParameterizedType() { result.getErasure() = this } + + /** + * Gets the raw type corresponding to this generic type. + * + * The raw version of this generic type is the type that is formed by + * using the name of this generic type without specifying its type arguments. + * + * For example, `List` is the raw version of the generic type + * `List`, where `E` is a type parameter. + */ + RawType getRawType() { result.getErasure() = this } + + /** + * Gets the `i`-th type parameter of this generic type. + */ + TypeVariable getTypeParameter(int i) { typeVars(result, _, i, _, this) } + + /** + * Gets a type parameter of this generic type. + */ + TypeVariable getATypeParameter() { result = getTypeParameter(_) } + + /** + * Gets the number of type parameters of this generic type. + */ + int getNumberOfTypeParameters() { result = strictcount(getATypeParameter()) } +} + +/** A generic type that is a class. */ +class GenericClass extends GenericType, Class { +} + +/** A generic type that is an interface. */ +class GenericInterface extends GenericType, Interface { +} + +/** + * A common super-class for Java types that may have a type bound. + * This includes type parameters and wildcards. + */ +abstract class BoundedType extends RefType, @boundedtype { + /** Holds if this type is bounded. */ + predicate hasTypeBound() { exists(TypeBound tb | tb = this.getATypeBound()) } + + /** Gets a type bound for this type, if any. */ + TypeBound getATypeBound() { result.getBoundedType() = this } + + /** Gets the first type bound for this type, if any. */ + TypeBound getFirstTypeBound() { result = getATypeBound() and result.getPosition() = 0 } + + /** + * Gets an upper type bound of this type, or `Object` + * if no explicit type bound is present. + */ + abstract RefType getUpperBoundType(); + + /** + * Gets the first upper type bound of this type, or `Object` + * if no explicit type bound is present. + */ + abstract RefType getFirstUpperBoundType(); + + /** Gets a transitive upper bound for this type that is not itself a bounded type. */ + RefType getAnUltimateUpperBoundType() { + result = getUpperBoundType() and not result instanceof BoundedType or + result = getUpperBoundType().(BoundedType).getAnUltimateUpperBoundType() + } +} + +/** + * A type parameter used in the declaration of a generic type or method. + * + * For example, `T` is a type parameter in + * `class X { }` and in ` void m() { }`. + */ +class TypeVariable extends BoundedType, @typevariable { + /** Gets the generic type that is parameterized by this type parameter, if any. */ + RefType getGenericType() { typeVars(this,_,_,_,result) } + + /** Gets the generic callable that is parameterized by this type parameter, if any. */ + GenericCallable getGenericCallable() { typeVars(this,_,_,_,result) } + + /** + * Gets an upper bound of this type parameter, or `Object` + * if no explicit type bound is present. + */ + pragma[nomagic] + override RefType getUpperBoundType() { + if this.hasTypeBound() then + result = this.getATypeBound().getType() + else + result instanceof TypeObject + } + + /** + * Gets the first upper bound of this type parameter, or `Object` + * if no explicit type bound is present. + */ + pragma[nomagic] + override RefType getFirstUpperBoundType() { + if this.hasTypeBound() then + result = this.getFirstTypeBound().getType() + else + result instanceof TypeObject + } + + /** Gets the lexically enclosing package of this type parameter, if any. */ + override Package getPackage() { + result = getGenericType().getPackage() or + result = getGenericCallable().getDeclaringType().getPackage() + } + + /** Finds a type that was supplied for this parameter. */ + RefType getASuppliedType() { + exists(RefType typearg | + exists(GenericType gen, int pos | + this = gen.getTypeParameter(pos) and + typearg = gen.getAParameterizedType().getTypeArgument(pos) + ) or + typearg = any(GenericCall call).getATypeArgument(this) + | + if typearg.(Wildcard).isUnconstrained() and this.hasTypeBound() then + result.(Wildcard).getUpperBound().getType() = this.getUpperBoundType() + else + result = typearg + ) + } + + /** Finds a non-typevariable type that was transitively supplied for this parameter. */ + RefType getAnUltimatelySuppliedType() { + result = getASuppliedType() and not result instanceof TypeVariable or + result = getASuppliedType().(TypeVariable).getAnUltimatelySuppliedType() + } +} + +/** + * A wildcard used as a type argument. + * + * For example, in + * + * ``` + * Map + * ``` + * the first wildcard has an upper bound of `Number` + * and the second wildcard has a lower bound of `Float`. + */ +class Wildcard extends BoundedType, @wildcard { + /** Holds if this wildcard has an upper bound. */ + predicate hasUpperBound() { + wildcards(this, _, 1) + } + + /** Holds if this wildcard has a lower bound. */ + predicate hasLowerBound() { + wildcards(this, _, 2) + } + + /** Gets the upper bound for this wildcard, if any. */ + TypeBound getUpperBound() { + this.hasUpperBound() and result = this.getATypeBound() + } + + /** + * Gets an upper bound type of this wildcard, or `Object` + * if no explicit type bound is present. + */ + override RefType getUpperBoundType() { + if this.hasUpperBound() then + result = this.getUpperBound().getType() + else + result instanceof TypeObject + } + + /** + * Gets the first upper bound type of this wildcard, or `Object` + * if no explicit type bound is present. + */ + override RefType getFirstUpperBoundType() { + if this.hasUpperBound() then + result = this.getFirstTypeBound().getType() + else + result instanceof TypeObject + } + + /** Gets the lower bound of this wildcard, if any. */ + TypeBound getLowerBound() { + this.hasLowerBound() and result = this.getATypeBound() + } + + /** + * Gets the lower bound type for this wildcard, + * if an explicit lower bound is present. + */ + Type getLowerBoundType() { + result = this.getLowerBound().getType() + } + + /** + * Holds if this is the unconstrained wildcard `?`. + */ + predicate isUnconstrained() { + not hasLowerBound() and + wildcards(this, "?", _) + } +} + +/** + * A type bound on a type variable. + * + * For example, `Number` is a type bound on the type variable + * `T` in `class X { }`. + * + * Type variables can have multiple type bounds, specified by + * an intersection type `T0 & T1 & ... & Tn`. + * A bound with position 0 is an interface type or class type (possibly `Object`) and + * a bound with a non-zero position is an interface type. + */ +class TypeBound extends @typebound { + /** + * Gets the type variable that is bounded by this type bound. + * + * For example, `T` is the type variable bounded by the + * type `Number` in `T extends Number`. + */ + BoundedType getBoundedType() { typeBounds(this,_,_,result) } + + /** + * Gets the type of this bound. + * + * For example, `Number` is the type of the bound (of + * the type variable `T`) in `T extends Number`. + */ + RefType getType() { typeBounds(this,result,_,_) } + + /** + * Gets the (zero-indexed) position of this bound. + * + * For example, in + * + * ``` + * class X { } + * ``` + * the position of the bound `Runnable` is 0 and + * the position of the bound `Cloneable` is 1. + */ + int getPosition() { typeBounds(this,_,result,_) } + + /** Gets a textual representation of this type bound. */ + string toString() { result = this.getType().getName() } +} + +// -------- Parameterizations of generic types -------- + +/** + * A parameterized type is an instantiation of a generic type, where + * each formal type variable has been replaced with a type argument. + * + * For example, `List` is a parameterization of + * the generic type `List`, where `E` is a type parameter. + */ +class ParameterizedType extends RefType { + ParameterizedType() { + typeArgs(_,_,this) or + typeVars(_,_,_,_,this) + } + + /** + * The erasure of a parameterized type is its generic counterpart. + * + * For example, the erasure of both `X` and `X` is `X`. + */ + override RefType getErasure() { erasure(this,result) or this.(GenericType) = result } + + /** + * Gets the generic type corresponding to this parameterized type. + * + * For example, the generic type for both `X` and `X` is `X`. + */ + GenericType getGenericType() { result.getAParameterizedType() = this } + + /** + * Gets a type argument for this parameterized type. + * + * For example, `Number` in `List`. + */ + RefType getATypeArgument() { + typeArgs(result,_,this) or + typeVars(result,_,_,_,this) + } + + /** Gets the type argument of this parameterized type at the specified position. */ + RefType getTypeArgument(int pos) { + typeArgs(result,pos,this) or + typeVars(result,_,pos,_,this) + } + + /** Gets the number of type arguments of this parameterized type. */ + int getNumberOfTypeArguments() { + result = count(int pos | + typeArgs(_,pos,this) or + typeVars(_,_,pos,_,this) + ) + } + + /** Holds if this type originates from source code. */ + override predicate fromSource() { typeVars(_,_,_,_,this) and RefType.super.fromSource() } +} + +/** A parameterized type that is a class. */ +class ParameterizedClass extends Class, ParameterizedType { +} + +/** A parameterized type that is an interface. */ +class ParameterizedInterface extends Interface, ParameterizedType { +} + +/** + * The raw version of a generic type is the type that is formed by + * using the name of a generic type without specifying its type arguments. + * + * For example, `List` is the raw version of the generic type + * `List`, where `E` is a type parameter. + * + * Raw types typically occur in legacy code that was written + * prior to the introduction of generic types in Java 5. + */ +class RawType extends RefType { + RawType() { isRaw(this) } + + /** + * The erasure of a raw type is its generic counterpart. + * + * For example, the erasure of `List` is `List`. + */ + override RefType getErasure() { erasure(this,result) } + + /** Holds if this type originates from source code. */ + override predicate fromSource() { not any() } +} + +/** A raw type that is a class. */ +class RawClass extends Class, RawType { +} + +/** A raw type that is an interface. */ +class RawInterface extends Interface, RawType { +} + +// -------- Generic callables -------- + +/** + * A generic callable is a callable with a type parameter. + */ +class GenericCallable extends Callable { + GenericCallable() { + exists(Callable srcDecl | + methods(this,_,_,_,_,srcDecl) or constrs(this,_,_,_,_,srcDecl) + | + typeVars(_,_,_,_,srcDecl) + ) + } + + /** + * Gets the `i`-th type parameter of this generic callable. + */ + TypeVariable getTypeParameter(int i) { typeVars(result, _, i, _, this.getSourceDeclaration()) } + + /** + * Gets a type parameter of this generic callable. + */ + TypeVariable getATypeParameter() { result = getTypeParameter(_) } + + /** + * Gets the number of type parameters of this generic callable. + */ + int getNumberOfTypeParameters() { result = strictcount(getATypeParameter()) } +} + +/** + * A call where the callee is a generic callable. + */ +class GenericCall extends Call { + GenericCall() { + this.getCallee() instanceof GenericCallable + } + + private RefType getAnInferredTypeArgument(TypeVariable v) { + typevarArg(this, v, result) + or + not typevarArg(this, v, _) and + v = this.getCallee().(GenericCallable).getATypeParameter() and + result.(Wildcard).getUpperBound().getType() = v.getUpperBoundType() + } + + private RefType getAnExplicitTypeArgument(TypeVariable v) { + exists(GenericCallable gen, MethodAccess call, int i | + this = call and + gen = call.getCallee() and + v = gen.getTypeParameter(i) and + result = call.getTypeArgument(i).getType() + ) + } + + /** Gets a type argument of the call for the given `TypeVariable`. */ + RefType getATypeArgument(TypeVariable v) { + result = getAnExplicitTypeArgument(v) or + not exists(getAnExplicitTypeArgument(v)) and + result = getAnInferredTypeArgument(v) + } +} + +/** Infers a type argument of `call` for `v` using a simple unification. */ +private predicate typevarArg(Call call, TypeVariable v, RefType typearg) { + exists(GenericCallable gen | + gen = call.getCallee() and + v = gen.getATypeParameter() + | + hasSubstitution(gen.getReturnType(), call.(Expr).getType(), v, typearg) + or + exists(int n | hasSubtypedSubstitution(gen.getParameterType(n), call.getArgument(n).getType(), v, typearg)) + ) +} + +/** + * The reflexive transitive closure of `RefType.extendsOrImplements` including reflexivity on `Type`s. + */ +private Type getShallowSupertype(Type t) { + result = t or t.(RefType).extendsOrImplements+(result) +} + +/** + * Manual magic sets optimization for the "inputs" of `hasSubstitution` and + * `hasParameterSubstitution`. + */ +private predicate unificationTargets(RefType t1, Type t2) { + exists(GenericCallable gen, Call call | + gen = call.getCallee() + | + t1 = gen.getReturnType() and t2 = call.(Expr).getType() or + exists(int n | t1 = gen.getParameterType(n) and t2 = getShallowSupertype(call.getArgument(n).getType())) + ) or + exists(Array a1, Array a2 | + unificationTargets(a1, a2) and + t1 = a1.getComponentType() and + t2 = a2.getComponentType() + ) or + exists(ParameterizedType pt1, ParameterizedType pt2, int pos | + unificationTargets(pt1, pt2) and + t1 = pt1.getTypeArgument(pos) and + t2 = pt2.getTypeArgument(pos) + ) +} + +/** + * Unifies `t1` and `t2` with respect to `v` allowing subtyping. + * + * `t1` contains `v` and equals a supertype of `t2` if `subst` is substituted for `v`. + * Only shallow supertypes of `t2` are considered in order to get the most precise result for `subst`. + * For example: + * If `t1` is `List` and `t2` is `ArrayList` we only want `subst` to be `T` and not, say, `?`. + */ +pragma[nomagic] +private predicate hasSubtypedSubstitution(RefType t1, Type t2, TypeVariable v, RefType subst) { + hasSubstitution(t1, t2, v, subst) or + exists(GenericType g | hasParameterSubstitution(g, t1, g, getShallowSupertype(t2), v, subst)) +} + +/** + * Unifies `t1` and `t2` with respect to `v`. + * + * `t1` contains `v` and equals `t2` if `subst` is substituted for `v`. + * As a special case `t2` can be a primitive type and the equality hold when + * `t2` is auto-boxed. + */ +private predicate hasSubstitution(RefType t1, Type t2, TypeVariable v, RefType subst) { + unificationTargets(t1, t2) and + ( + t1 = v and (t2 = subst or t2.(PrimitiveType).getBoxedType() = subst) or + hasSubstitution(t1.(Array).getComponentType(), t2.(Array).getComponentType(), v, subst) or + exists(GenericType g | hasParameterSubstitution(g, t1, g, t2, v, subst)) + ) +} + +private predicate hasParameterSubstitution(GenericType g1, ParameterizedType pt1, GenericType g2, ParameterizedType pt2, TypeVariable v, RefType subst) { + unificationTargets(pt1, pt2) and + exists(int pos | + hasSubstitution(pt1.getTypeArgument(pos), pt2.getTypeArgument(pos), v, subst) + ) and + g1 = pt1.getGenericType() and + g2 = pt2.getGenericType() +} + +/** + * A generic constructor is a constructor with a type parameter. + * + * For example, ` C(T t) { }` is a generic constructor for type `C`. + */ +class GenericConstructor extends Constructor, GenericCallable { + override GenericConstructor getSourceDeclaration() { result = Constructor.super.getSourceDeclaration() } + override ConstructorCall getAReference() { result = Constructor.super.getAReference() } +} + +/** + * A generic method is a method with a type parameter. + * + * For example, ` void m(T t) { }` is a generic method. + */ +class GenericMethod extends Method, GenericCallable { + override GenericSrcMethod getSourceDeclaration() { result = Method.super.getSourceDeclaration() } +} + +/** A generic method that is the same as its source declaration. */ +class GenericSrcMethod extends SrcMethod, GenericMethod { +} diff --git a/java/ql/src/semmle/code/java/Import.qll b/java/ql/src/semmle/code/java/Import.qll new file mode 100755 index 00000000000..90a85595a8a --- /dev/null +++ b/java/ql/src/semmle/code/java/Import.qll @@ -0,0 +1,140 @@ +/** + * Provides classes and predicates for working with Java imports. + */ + +import semmle.code.Location +import CompilationUnit + +/** A common super-class for all kinds of Java import declarations. */ +class Import extends Element, @import { + /** Gets the compilation unit in which this import declaration occurs. */ + override CompilationUnit getCompilationUnit() { result = this.getFile() } + + /** Holds if this import declaration occurs in source code. */ + override predicate fromSource() { any() } + + /*abstract*/ override string toString() { result = "import" } +} + +/** + * A single-type-import declaration. + * + * For example, `import java.util.Set;`. + */ +class ImportType extends Import { + ImportType() { imports(this,_,_,1) } + + /** Gets the imported type. */ + RefType getImportedType() { imports(this,result,_,_) } + + override string toString() { result = "import " + this.getImportedType().toString() } +} + +/** + * A type-import-on-demand declaration that allows all accessible + * nested types of a named type to be imported as needed. + * + * For example, `import java.util.Map.*;` imports + * the nested type `java.util.Map.Entry` from the type + * `java.util.Map`. + */ +class ImportOnDemandFromType extends Import { + ImportOnDemandFromType() { imports(this,_,_,2) } + + /** Gets the type from which accessible nested types are imported. */ + RefType getTypeHoldingImport() { imports(this,result,_,_) } + + /** Gets an imported type. */ + NestedType getAnImport() { result.getEnclosingType() = this.getTypeHoldingImport() } + + override string toString() { + result = "import " + this.getTypeHoldingImport().toString() + ".*" + } +} + +/** + * A type-import-on-demand declaration that allows all accessible + * types of a named package to be imported as needed. + * + * For example, `import java.util.*;`. + */ +class ImportOnDemandFromPackage extends Import { + ImportOnDemandFromPackage() { imports(this,_,_,3) } + + /** Gets the package from which accessible types are imported. */ + Package getPackageHoldingImport() { imports(this,result,_,_) } + + /** Gets an imported type. */ + RefType getAnImport() { result.getPackage() = this.getPackageHoldingImport() } + + /** Gets a printable representation of this import declaration. */ + override string toString() { + result = "import " + this.getPackageHoldingImport().toString() + ".*" + } +} + +/** + * A static-import-on-demand declaration, which allows all accessible + * static members of a named type to be imported as needed. + * + * For example, `import static java.lang.System.*;`. + */ +class ImportStaticOnDemand extends Import { + ImportStaticOnDemand() { imports(this,_,_,4) } + + /** Gets the type from which accessible static members are imported. */ + RefType getTypeHoldingImport() { imports(this,result,_,_) } + + /** Gets an imported type. */ + NestedType getATypeImport() { result.getEnclosingType() = this.getTypeHoldingImport() } + + /** Gets an imported method. */ + Method getAMethodImport() { result.getDeclaringType() = this.getTypeHoldingImport() } + + /** Gets an imported field. */ + Field getAFieldImport() { result.getDeclaringType() = this.getTypeHoldingImport() } + + /** Gets a printable representation of this import declaration. */ + override string toString() { + result = "import static " + this.getTypeHoldingImport().toString() + ".*" + } +} + +/** + * A single-static-import declaration, which imports all accessible + * static members with a given simple name from a type. + * + * For example, `import static java.util.Collections.sort;` + * imports all the static methods named `sort` from the + * class `java.util.Collections`. + */ +class ImportStaticTypeMember extends Import { + ImportStaticTypeMember() { imports(this,_,_,5) } + + /** Gets the type from which static members with a given name are imported. */ + RefType getTypeHoldingImport() { imports(this,result,_,_) } + + /** Gets the name of the imported member(s). */ + override string getName() { imports(this,_,result,_) } + + /** Gets an imported member. */ + Member getAMemberImport() { + this.getTypeHoldingImport().getAMember() = result and + result.getName() = this.getName() and result.isStatic() + } + + /** Gets an imported type. */ + NestedType getATypeImport() { result = this.getAMemberImport() } + + /** Gets an imported method. */ + Method getAMethodImport() { result = this.getAMemberImport() } + + /** Gets an imported field. */ + Field getAFieldImport() { result = this.getAMemberImport() } + + /** Gets a printable representation of this import declaration. */ + override string toString() { + result = "import static " + this.getTypeHoldingImport().toString() + + "." + this.getName() + } +} diff --git a/java/ql/src/semmle/code/java/J2EE.qll b/java/ql/src/semmle/code/java/J2EE.qll new file mode 100755 index 00000000000..783d8e7c66c --- /dev/null +++ b/java/ql/src/semmle/code/java/J2EE.qll @@ -0,0 +1,77 @@ +/** + * Provides classes and predicates for working with J2EE bean types. + */ + +import Type + +/** An entity bean. */ +class EntityBean extends Class { + EntityBean() { + exists(Interface i | i.hasQualifiedName("javax.ejb","EntityBean") | + this.hasSupertype+(i) + ) + } +} + +/** An enterprise bean. */ +class EnterpriseBean extends RefType { + EnterpriseBean() { + exists(Interface i | i.hasQualifiedName("javax.ejb","EnterpriseBean") | + this.hasSupertype+(i) + ) + } +} + +/** A local EJB home interface. */ +class LocalEJBHomeInterface extends Interface { + LocalEJBHomeInterface() { + exists(Interface i | i.hasQualifiedName("javax.ejb","EJBLocalHome") | + this.hasSupertype+(i) + ) + } +} + +/** A remote EJB home interface. */ +class RemoteEJBHomeInterface extends Interface { + RemoteEJBHomeInterface() { + exists(Interface i | i.hasQualifiedName("javax.ejb","EJBHome") | + this.hasSupertype+(i) + ) + } +} + +/** A local EJB interface. */ +class LocalEJBInterface extends Interface { + LocalEJBInterface() { + exists(Interface i | i.hasQualifiedName("javax.ejb","EJBLocalObject") | + this.hasSupertype+(i) + ) + } +} + +/** A remote EJB interface. */ +class RemoteEJBInterface extends Interface { + RemoteEJBInterface() { + exists(Interface i | i.hasQualifiedName("javax.ejb","EJBObject") | + this.hasSupertype+(i) + ) + } +} + +/** A message bean. */ +class MessageBean extends Class { + MessageBean() { + exists(Interface i | i.hasQualifiedName("javax.ejb","MessageDrivenBean") | + this.hasSupertype+(i) + ) + } +} + +/** A session bean. */ +class SessionBean extends Class { + SessionBean() { + exists(Interface i | i.hasQualifiedName("javax.ejb","SessionBean") | + this.hasSupertype+(i) + ) + } +} diff --git a/java/ql/src/semmle/code/java/JDK.qll b/java/ql/src/semmle/code/java/JDK.qll new file mode 100644 index 00000000000..73ec22b700e --- /dev/null +++ b/java/ql/src/semmle/code/java/JDK.qll @@ -0,0 +1,428 @@ +/** + * Provides classes and predicates for working with standard classes and methods from the JDK. + */ + +import Member + +// --- Standard types --- + +/** The class `java.lang.Object`. */ +class TypeObject extends Class { + pragma[noinline] + TypeObject() { + this.hasQualifiedName("java.lang", "Object") + } +} + +/** The interface `java.lang.Cloneable`. */ +class TypeCloneable extends Interface { + TypeCloneable() { + this.hasQualifiedName("java.lang", "Cloneable") + } +} + +/** The class `java.lang.ProcessBuilder`. */ +class TypeProcessBuilder extends Class { + TypeProcessBuilder() { + hasQualifiedName("java.lang", "ProcessBuilder") + } +} + +/** The class `java.lang.Runtime`. */ +class TypeRuntime extends Class { + TypeRuntime() { + hasQualifiedName("java.lang", "Runtime") + } +} + +/** The class `java.lang.String`. */ +class TypeString extends Class { + TypeString() { + this.hasQualifiedName("java.lang", "String") + } +} + +/** The `length()` method of the class `java.lang.String`. */ +class StringLengthMethod extends Method { + StringLengthMethod() { + this.hasName("length") and this.getDeclaringType() instanceof TypeString + } +} + +/** The class `java.lang.StringBuffer`. */ +class TypeStringBuffer extends Class { + TypeStringBuffer() { + this.hasQualifiedName("java.lang", "StringBuffer") + } +} + +/** The class `java.lang.StringBuilder`. */ +class TypeStringBuilder extends Class { + TypeStringBuilder() { + this.hasQualifiedName("java.lang", "StringBuilder") + } +} + +/** The class `java.lang.System`. */ +class TypeSystem extends Class { + TypeSystem() { + this.hasQualifiedName("java.lang", "System") + } +} + +/** The class `java.lang.Throwable`. */ +class TypeThrowable extends Class { + TypeThrowable() { + this.hasQualifiedName("java.lang", "Throwable") + } +} + +/** The class `java.lang.Exception`. */ +class TypeException extends Class { + TypeException() { + this.hasQualifiedName("java.lang", "Exception") + } +} + +/** The class `java.lang.Error`. */ +class TypeError extends Class { + TypeError() { + this.hasQualifiedName("java.lang", "Error") + } +} + +/** The class `java.lang.RuntimeException`. */ +class TypeRuntimeException extends Class { + TypeRuntimeException() { + this.hasQualifiedName("java.lang", "RuntimeException") + } +} + +/** The class `java.lang.ClassCastException`. */ +class TypeClassCastException extends Class { + TypeClassCastException() { + this.hasQualifiedName("java.lang", "ClassCastException") + } +} + +/** + * The class `java.lang.Class`. + * + * This includes the generic source declaration, any parameterized instances and the raw type. + */ +class TypeClass extends Class { + TypeClass() { + this.getSourceDeclaration().hasQualifiedName("java.lang", "Class") + } +} + +/** + * The class `java.lang.Constructor`. + * + * This includes the generic source declaration, any parameterized instances and the raw type. + */ +class TypeConstructor extends Class { + TypeConstructor() { + this.getSourceDeclaration().hasQualifiedName("java.lang.reflect", "Constructor") + } +} + +/** The class `java.lang.Math`. */ +class TypeMath extends Class { + TypeMath() { + this.hasQualifiedName("java.lang", "Math") + } +} + +/** A numeric type, including both primitive and boxed types. */ +class NumericType extends Type { + NumericType() { + exists(string name | + name = this.(PrimitiveType).getName() or + name = this.(BoxedType).getPrimitiveType().getName() + | + name.regexpMatch("byte|short|int|long|double|float") + ) + } +} + +/** An immutable type. */ +class ImmutableType extends Type { + ImmutableType() { + this instanceof PrimitiveType or + this instanceof NullType or + this instanceof VoidType or + this instanceof BoxedType or + this instanceof TypeString + } +} + +// --- Java IO --- + +/** The interface `java.io.Serializable`. */ +class TypeSerializable extends Interface { + TypeSerializable() { + hasQualifiedName("java.io", "Serializable") + } +} + +/** The interface `java.io.ObjectOutput`. */ +class TypeObjectOutput extends Interface { + TypeObjectOutput() { + hasQualifiedName("java.io", "ObjectOutput") + } +} + +/** The type `java.io.ObjectOutputStream`. */ +class TypeObjectOutputStream extends RefType { + TypeObjectOutputStream() { + hasQualifiedName("java.io", "ObjectOutputStream") + } +} + +/** The class `java.nio.file.Paths`. */ +class TypePaths extends Class { + TypePaths() { + this.hasQualifiedName("java.nio.file", "Paths") + } +} + +/** The class `java.nio.file.Path`. */ +class TypePath extends Class { + TypePath() { + this.hasQualifiedName("java.nio.file", "Path") + } +} + +/** The class `java.nio.file.FileSystem`. */ +class TypeFileSystem extends Class { + TypeFileSystem() { + this.hasQualifiedName("java.nio.file", "FileSystem") + } +} + +/** The class `java.io.File`. */ +class TypeFile extends Class { + TypeFile() { + this.hasQualifiedName("java.io", "File") + } +} + +// --- Standard methods --- + +/** + * Any of the methods named `command` on class `java.lang.ProcessBuilder`. + */ +class MethodProcessBuilderCommand extends Method { + MethodProcessBuilderCommand() { + hasName("command") and + getDeclaringType() instanceof TypeProcessBuilder + } +} + +/** + * Any method named `exec` on class `java.lang.Runtime`. + */ +class MethodRuntimeExec extends Method { + MethodRuntimeExec() { + hasName("exec") and + getDeclaringType() instanceof TypeRuntime + } +} + +/** + * Any method named `getenv` on class `java.lang.System`. + */ +class MethodSystemGetenv extends Method { + MethodSystemGetenv() { + hasName("getenv") and + getDeclaringType() instanceof TypeSystem + } +} + +/** + * Any method named `getProperty` on class `java.lang.System`. + */ +class MethodSystemGetProperty extends Method { + MethodSystemGetProperty() { + hasName("getProperty") and + getDeclaringType() instanceof TypeSystem + } +} + +/** + * Any method named `exit` on class `java.lang.Runtime` or `java.lang.System`. + */ +class MethodExit extends Method { + MethodExit() { + hasName("exit") and + (getDeclaringType() instanceof TypeRuntime or getDeclaringType() instanceof TypeSystem) + } +} + +/** + * A method named `writeObject` on type `java.io.ObjectOutput` + * or `java.io.ObjectOutputStream`. + */ +class WriteObjectMethod extends Method { + WriteObjectMethod() { + hasName("writeObject") and + ( + getDeclaringType() instanceof TypeObjectOutputStream or + getDeclaringType() instanceof TypeObjectOutput + ) + } +} + +/** + * A method that reads an object on type `java.io.ObjectInputStream`, + * including `readObject`, `readObjectOverride`, `readUnshared` and `resolveObject`. + */ +class ReadObjectMethod extends Method { + ReadObjectMethod() { + this.getDeclaringType().hasQualifiedName("java.io", "ObjectInputStream") and + ( + this.hasName("readObject") or + this.hasName("readObjectOverride") or + this.hasName("readUnshared") or + this.hasName("resolveObject") + ) + } +} + +/** The method `Class.getName()`. */ +class ClassNameMethod extends Method { + ClassNameMethod() { + hasName("getName") and + getDeclaringType() instanceof TypeClass + } +} + +/** The method `Class.getSimpleName()`. */ +class ClassSimpleNameMethod extends Method { + ClassSimpleNameMethod() { + hasName("getSimpleName") and + getDeclaringType() instanceof TypeClass + } +} + +/** The method `Math.abs`. */ +class MethodAbs extends Method { + MethodAbs() { + this.getDeclaringType() instanceof TypeMath and + this.getName() = "abs" + } +} + +// --- Standard fields --- + +/** The field `System.in`. */ +class SystemIn extends Field { + SystemIn() { + hasName("in") and + getDeclaringType() instanceof TypeSystem + } +} + +/** The field `System.out`. */ +class SystemOut extends Field { + SystemOut() { + hasName("out") and + getDeclaringType() instanceof TypeSystem + } +} + +/** The field `System.err`. */ +class SystemErr extends Field { + SystemErr() { + hasName("err") and + getDeclaringType() instanceof TypeSystem + } +} + +// --- User-defined methods with a particular meaning --- + +/** A method with the same signature as `java.lang.Object.equals`. */ +class EqualsMethod extends Method { + EqualsMethod() { + this.hasName("equals") and + this.getNumberOfParameters() = 1 and + this.getParameter(0).getType().(RefType).hasQualifiedName("java.lang", "Object") + } + + /** Gets the single parameter of this method. */ + Parameter getParameter() { + result = this.getAParameter() + } +} + +/** A method with the same signature as `java.lang.Object.hashCode`. */ +class HashCodeMethod extends Method { + HashCodeMethod() { + this.hasName("hashCode") and + this.getNumberOfParameters() = 0 + } +} + +/** A method with the same signature as `java.lang.Object.clone`. */ +class CloneMethod extends Method { + CloneMethod() { + this.hasName("clone") and + this.hasNoParameters() + } +} + +/** + * The public static `main` method, with a single formal parameter + * of type `String[]` and return type `void`. + */ +class MainMethod extends Method { + MainMethod() { + this.isPublic() and + this.isStatic() and + this.getReturnType().hasName("void") and + this.hasName("main") and + this.getNumberOfParameters() = 1 and + exists(Array a | + a = this.getAParameter().getType() and + a.getDimension() = 1 and + a.getElementType() instanceof TypeString + ) + } +} + +/** A premain method is an agent entry-point. */ +class PreMainMethod extends Method { + PreMainMethod() { + this.isPublic() and + this.isStatic() and + this.getReturnType().hasName("void") and + this.getNumberOfParameters() < 3 and + this.getParameter(0).getType() instanceof TypeString and + (exists(this.getParameter(1)) implies + this.getParameter(1).getType().hasName("Instrumentation")) + } +} + +/** The `length` field of the array type. */ +class ArrayLengthField extends Field { + ArrayLengthField() { + this.getDeclaringType() instanceof Array and + this.hasName("length") + } +} + +/** A (reflexive, transitive) subtype of `java.lang.Throwable`. */ +class ThrowableType extends RefType { + ThrowableType() { + exists(TypeThrowable throwable | hasSubtype*(throwable, this)) + } +} + +/** An unchecked exception. That is, a (reflexive, transitive) subtype of `java.lang.Error` or `java.lang.RuntimeException`. */ +class UncheckedThrowableType extends RefType { + UncheckedThrowableType() { + exists(TypeError e | hasSubtype*(e, this)) or + exists(TypeRuntimeException e | hasSubtype*(e, this)) + } +} diff --git a/java/ql/src/semmle/code/java/JDKAnnotations.qll b/java/ql/src/semmle/code/java/JDKAnnotations.qll new file mode 100644 index 00000000000..12936f9f9ff --- /dev/null +++ b/java/ql/src/semmle/code/java/JDKAnnotations.qll @@ -0,0 +1,136 @@ +/** + * Provides classes that represent standard annotations from the JDK. + */ + +import java + +/** A `@Deprecated` annotation. */ +class DeprecatedAnnotation extends Annotation { + DeprecatedAnnotation() { + this.getType().hasQualifiedName("java.lang", "Deprecated") + } +} + +/** An `@Override` annotation. */ +class OverrideAnnotation extends Annotation { + OverrideAnnotation() { + this.getType().hasQualifiedName("java.lang", "Override") + } +} + +/** A `@SuppressWarnings` annotation. */ +class SuppressWarningsAnnotation extends Annotation { + SuppressWarningsAnnotation() { + this.getType().hasQualifiedName("java.lang", "SuppressWarnings") + } + + /** Gets the name of a warning suppressed by this annotation. */ + string getASuppressedWarning() { + result = this.getAValue().(StringLiteral).getLiteral() or + result = this.getAValue().(ArrayInit).getAnInit().(StringLiteral).getLiteral() + } +} + +/** A `@Target` annotation. */ +class TargetAnnotation extends Annotation { + TargetAnnotation() { + this.getType().hasQualifiedName("java.lang.annotation", "Target") + } + + /** + * Gets a target expression within this annotation. + * + * For example, the field access `ElementType.FIELD` is a target expression in + * `@Target({ElementType.FIELD, ElementType.METHOD})`. + */ + Expr getATargetExpression() { + not result instanceof ArrayInit and + ( + result = this.getAValue() or + result = this.getAValue().(ArrayInit).getAnInit() + ) + } + + /** + * Gets the name of a target element type. + * + * For example, `METHOD` is the name of a target element type in + * `@Target({ElementType.FIELD, ElementType.METHOD})`. + */ + string getATargetElementType() { + exists(EnumConstant ec | + ec = this.getATargetExpression().(VarAccess).getVariable() and + ec.getDeclaringType().hasQualifiedName("java.lang.annotation", "ElementType") + | + result = ec.getName()) + } +} + +/** A `@Retention` annotation. */ +class RetentionAnnotation extends Annotation { + RetentionAnnotation() { + this.getType().hasQualifiedName("java.lang.annotation", "Retention") + } + + /** + * Gets the retention policy expression within this annotation. + * + * For example, the field access `RetentionPolicy.RUNTIME` is the + * retention policy expression in `@Retention(RetentionPolicy.RUNTIME)`. + */ + Expr getRetentionPolicyExpression() { + result = this.getValue("value") + } + + /** + * Gets the name of the retention policy of this annotation. + * + * For example, `RUNTIME` is the name of the retention policy + * in `@Retention(RetentionPolicy.RUNTIME)`. + */ + string getRetentionPolicy() { + exists(EnumConstant ec | + ec = this.getRetentionPolicyExpression().(VarAccess).getVariable() and + ec.getDeclaringType().hasQualifiedName("java.lang.annotation", "RetentionPolicy") + | + result = ec.getName()) + } +} + +/** + * An annotation suggesting that the annotated element may be accessed reflectively. + * + * This is implemented by negation of a white-list of standard annotations that are + * known not to be reflection-related; all other annotations are assumed to potentially + * be reflection-related. + * + * A typical use-case is the exclusion of results relating to various frameworks, + * where an exhaustive list of all annotations for all frameworks that may exist + * can be difficult to obtain and maintain. + */ +class ReflectiveAccessAnnotation extends Annotation { + ReflectiveAccessAnnotation() { + // We conservatively white-list a few standard annotations that have nothing to do + // with reflection, and assume that any other annotation may be reflection-related. + not this instanceof NonReflectiveAnnotation + } +} + +/** + * An annotation that does not indicate that a field may be accessed reflectively. + * + * Any annotation that is not a subclass of `NonReflectiveAnnotation` is assumed to + * allow for reflective access. + */ +abstract class NonReflectiveAnnotation extends Annotation {} + +library class StandardNonReflectiveAnnotation extends NonReflectiveAnnotation { + StandardNonReflectiveAnnotation() { + exists(AnnotationType anntp | anntp = this.getType() | + anntp.hasQualifiedName("java.lang", "Override") or + anntp.hasQualifiedName("java.lang", "Deprecated") or + anntp.hasQualifiedName("java.lang", "SuppressWarnings") or + anntp.hasQualifiedName("java.lang", "SafeVarargs") + ) + } +} diff --git a/java/ql/src/semmle/code/java/JMX.qll b/java/ql/src/semmle/code/java/JMX.qll new file mode 100644 index 00000000000..d8693eab35b --- /dev/null +++ b/java/ql/src/semmle/code/java/JMX.qll @@ -0,0 +1,103 @@ +/** + * Provides classes and predicates for working with JMX bean types. + */ + +import Type + +/** A managed bean. */ +abstract class ManagedBean extends Interface { +} + +/** An `MBean`. */ +class MBean extends ManagedBean { + MBean() { + this.getQualifiedName().matches("%MBean%") + } +} + +/** An `MXBean`. */ +class MXBean extends ManagedBean { + MXBean() { + this.getQualifiedName().matches("%MXBean%") or + this.getAnAnnotation().getType().hasQualifiedName("javax.management", "MXBean") + } +} + +/** + * An managed bean implementation which is seen to be registered with the `MBeanServer`, directly or + * indirectly. + */ +class RegisteredManagedBeanImpl extends Class { + RegisteredManagedBeanImpl() { + getAnAncestor() instanceof ManagedBean and + exists(JMXRegistrationCall registerCall | + registerCall.getObjectArgument().getType() = this + ) + } + + /** + * Gets a managed bean that this registered bean class implements. + */ + ManagedBean getAnImplementedManagedBean() { + result = getAnAncestor() + } +} + +/** + * A call that registers an object with the `MBeanServer`, directly or indirectly. + */ +class JMXRegistrationCall extends MethodAccess { + JMXRegistrationCall() { + getCallee() instanceof JMXRegistrationMethod + } + + /** + * Gets the argument that represents the object in the registration call. + */ + Expr getObjectArgument() { + result = getArgument(getCallee().(JMXRegistrationMethod).getObjectPosition()) + } +} + +/** + * A method used to register `MBean` and `MXBean` instances with the `MBeanServer`. + * + * This is either the `registerMBean` method on `MBeanServer`, or it is a wrapper around that + * registration method. + */ +class JMXRegistrationMethod extends Method { + JMXRegistrationMethod() { + ( + // A direct registration with the `MBeanServer`. + getDeclaringType().hasQualifiedName("javax.management", "MBeanServer") and + getName() = "registerMBean" + ) or + /* + * The `MBeanServer` is often wrapped by an application specific management class, so identify + * methods that wrap a call to another `JMXRegistrationMethod`. + */ + exists(JMXRegistrationCall c | + /* + * This must be a call to another JMX registration method, where the object argument is an access + * of one of the parameters of this method. + */ + c.getObjectArgument().(VarAccess).getVariable() = getAParameter() + ) + } + + /** + * Gets the position of the parameter through which the "object" to be registered is passed. + */ + int getObjectPosition() { + ( + // Passed as the first argument to `registerMBean`. + getDeclaringType().hasQualifiedName("javax.management", "MBeanServer") and + getName() = "registerMBean" and + result = 0 + ) or + // Identify the position in this method where the object parameter should be passed. + exists(JMXRegistrationCall c | + c.getObjectArgument().(VarAccess).getVariable() = getParameter(result) + ) + } +} diff --git a/java/ql/src/semmle/code/java/Javadoc.qll b/java/ql/src/semmle/code/java/Javadoc.qll new file mode 100755 index 00000000000..8cab8a615d0 --- /dev/null +++ b/java/ql/src/semmle/code/java/Javadoc.qll @@ -0,0 +1,170 @@ +/** + * Provides classes and predicates for working with Javadoc documentation. + */ + +import semmle.code.Location +import Element + +/** A Javadoc parent is an element whose child can be some Javadoc documentation. */ +class JavadocParent extends @javadocParent, Top { + /** Gets a documentation element attached to this parent. */ + JavadocElement getAChild() { result.getParent() = this } + + /** Gets the child documentation element at the specified (zero-based) position. */ + JavadocElement getChild(int index) { + result = this.getAChild() and result.getIndex() = index + } + + /** Gets the number of documentation elements attached to this parent. */ + int getNumChild() { + result = count(getAChild()) + } + + /** Gets a documentation element with the specified Javadoc tag name. */ + JavadocTag getATag(string name) { + result = this.getAChild() and result.getTagName() = name + } + + /*abstract*/ override string toString() { result = "Javadoc" } +} + +/** A Javadoc comment. */ +class Javadoc extends JavadocParent, @javadoc { + + /** Gets the number of lines in this Javadoc comment. */ + int getNumberOfLines() { + result = this.getLocation().getNumberOfCommentLines() + } + + /** Gets the value of the `@version` tag, if any. */ + string getVersion() { + result = this.getATag("@version").getChild(0).toString() + } + + /** Gets the value of the `@author` tag, if any. */ + string getAuthor() { + result = this.getATag("@author").getChild(0).toString() + } + + override string toString() { + result = toStringPrefix() + getChild(0) + toStringPostfix() + } + + private string toStringPrefix() { + if isEolComment(this) then + result = "//" + else ( + if isNormalComment(this) then + result = "/* " + else + result = "/** " + ) + } + + private string toStringPostfix() { + if isEolComment(this) then + result = "" + else ( + if strictcount(getAChild()) = 1 then + result = " */" + else + result = " ... */" + ) + } + + /** Gets the Java code element that is commented by this piece of Javadoc. */ + Documentable getCommentedElement() { result.getJavadoc() = this } +} + +/** A documentable element that can have an attached Javadoc comment. */ +class Documentable extends Element, @member { + /** Gets the Javadoc comment attached to this element. */ + Javadoc getJavadoc() { hasJavadoc(this,result) and not isNormalComment(result) } + + /** Gets the name of the author(s) of this element, if any. */ + string getAuthor() { + result = this.getJavadoc().getAuthor() + } +} + +/** A common super-class for Javadoc elements, which may be either tags or text. */ +abstract class JavadocElement extends @javadocElement, Top { + /** Gets the parent of this Javadoc element. */ + JavadocParent getParent() { + javadocTag(this,_,result,_) or javadocText(this,_,result,_) + } + + /** Gets the index of this child element relative to its parent. */ + int getIndex() { + javadocTag(this,_,_,result) or javadocText(this,_,_,result) + } + + /** Gets a printable representation of this Javadoc element. */ + /*abstract*/ override string toString() { result = "Javadoc element" } + + /** Gets the line of text associated with this Javadoc element. */ + abstract string getText(); +} + +/** A Javadoc tag. */ +class JavadocTag extends JavadocElement, JavadocParent, @javadocTag { + /** Gets the name of this Javadoc tag. */ + string getTagName() { javadocTag(this,result,_,_) } + + /** Gets a printable representation of this Javadoc tag. */ + override string toString() { result = this.getTagName() } + + /** Gets the text associated with this Javadoc tag. */ + override string getText() { result = this.getChild(0).toString() } +} + +/** A Javadoc `@param` tag. */ +class ParamTag extends JavadocTag { + ParamTag() { this.getTagName() = "@param" } + + /** Gets the name of the parameter. */ + string getParamName() { result = this.getChild(0).toString() } + + /** Gets the documentation for the parameter. */ + override string getText() { result = this.getChild(1).toString() } +} + +/** A Javadoc `@throws` or `@exception` tag. */ +class ThrowsTag extends JavadocTag { + ThrowsTag() { this.getTagName() = "@throws" or this.getTagName() = "@exception" } + + /** Gets the name of the exception. */ + string getExceptionName() { result = this.getChild(0).toString() } + + /** Gets the documentation for the exception. */ + override string getText() { result = this.getChild(1).toString() } +} + +/** A Javadoc `@see` tag. */ +class SeeTag extends JavadocTag { + SeeTag() { getTagName() = "@see" } + + /** Gets the name of the entity referred to. */ + string getReference() { result = getChild(0).toString() } +} + +/** A Javadoc `@author` tag. */ +class AuthorTag extends JavadocTag { + AuthorTag() { this.getTagName() = "@author" } + + /** Gets the name of the author. */ + string getAuthorName() { result = this.getChild(0).toString() } +} + +/** A piece of Javadoc text. */ +class JavadocText extends JavadocElement, @javadocText { + + /** Gets the Javadoc comment that contains this piece of text. */ + Javadoc getJavadoc() { result.getAChild+() = this } + + /** Gets the text itself. */ + override string getText() { javadocText(this,result,_,_) } + + /** Gets a printable representation of this Javadoc element. */ + override string toString() { result = this.getText() } +} diff --git a/java/ql/src/semmle/code/java/Maps.qll b/java/ql/src/semmle/code/java/Maps.qll new file mode 100644 index 00000000000..b259637de22 --- /dev/null +++ b/java/ql/src/semmle/code/java/Maps.qll @@ -0,0 +1,102 @@ +import java +import Collections + +/** A reference type that extends a parameterization of `java.util.Map`. */ +class MapType extends RefType { + MapType() { + exists(ParameterizedInterface coll | + coll.getSourceDeclaration().hasQualifiedName("java.util", "Map") + | + this.hasSupertype*(coll) + ) + } + + /** Gets the type of keys stored in this map. */ + RefType getKeyType() { + exists(GenericInterface map | map.hasQualifiedName("java.util", "Map") | + indirectlyInstantiates(this, map, 0, result) + ) + } + + /** Gets the type of values stored in this map. */ + RefType getValueType() { + exists(GenericInterface map | map.hasQualifiedName("java.util", "Map") | + indirectlyInstantiates(this, map, 1, result) + ) + } +} + +/** A method declared in a map type. */ +class MapMethod extends Method { + MapMethod() { + this.getDeclaringType() instanceof MapType + } + + /** Gets the type of keys of the map to which this method belongs. */ + RefType getReceiverKeyType() { + result = this.getDeclaringType().(MapType).getKeyType() + } + + /** Gets the type of values of the map to which this method belongs. */ + RefType getReceiverValueType() { + result = this.getDeclaringType().(MapType).getValueType() + } +} + +/** A method that mutates the map it belongs to. */ +class MapMutator extends MapMethod { + MapMutator() { + this.getName().regexpMatch("(put.*|remove|clear)") + } +} + +/** The `size` method of `java.util.Map`. */ +class MapSizeMethod extends MapMethod { + MapSizeMethod() { + this.hasName("size") and this.hasNoParameters() + } +} + +/** A method call that mutates a map. */ +class MapMutation extends MethodAccess { + MapMutation() { + this.getMethod() instanceof MapMutator + } + + predicate resultIsChecked() { + not this.getParent() instanceof ExprStmt + } +} + +/** A method that queries the contents of the map it belongs to without mutating it. */ +class MapQueryMethod extends MapMethod { + MapQueryMethod() { + this.getName().regexpMatch("get|containsKey|containsValue|entrySet|keySet|values|isEmpty|size") + } +} + +/** A `new` expression that allocates a fresh, empty map. */ +class FreshMap extends ClassInstanceExpr { + FreshMap() { + this.getConstructedType() instanceof MapType and + this.getNumArgument() = 0 and + not exists(this.getAnonymousClass()) + } +} + +/** + * A call to `Map.put(key, value)`. + */ +class MapPutCall extends MethodAccess { + MapPutCall() { + getCallee().(MapMethod).hasName("put") + } + + Expr getKey() { + result = getArgument(0) + } + + Expr getValue() { + result = getArgument(1) + } +} diff --git a/java/ql/src/semmle/code/java/Member.qll b/java/ql/src/semmle/code/java/Member.qll new file mode 100755 index 00000000000..829ce4386ef --- /dev/null +++ b/java/ql/src/semmle/code/java/Member.qll @@ -0,0 +1,645 @@ +/** + * Provides classes and predicates for working with members of Java classes and interfaces, + * that is, methods, constructors, fields and nested types. + */ + +import Element +import Type +import Annotation +import Exception +import metrics.MetricField + +/** + * A common abstraction for type member declarations, + * including methods, constructors, fields, and nested types. + */ +class Member extends Element, Annotatable, Modifiable, @member { + Member() { + declaresMember(_,this) + } + + /** Gets the type in which this member is declared. */ + RefType getDeclaringType() { declaresMember(result, this) } + + /** Gets the qualified name of this member. */ + string getQualifiedName() { + result = getDeclaringType().getName() + "." + getName() + } + + /** Holds if this member is package protected, that is, neither public nor private nor protected. */ + predicate isPackageProtected() { + not isPrivate() and + not isProtected() and + not isPublic() + } + + /** + * Gets the immediately enclosing callable, if this member is declared in + * an anonymous or local class. + */ + Callable getEnclosingCallable() { + exists(NestedClass nc | this.getDeclaringType() = nc | + nc.(AnonymousClass).getClassInstanceExpr().getEnclosingCallable() = result or + nc.(LocalClass).getLocalClassDeclStmt().getEnclosingCallable() = result + ) + } +} + +/** A callable is a method or constructor. */ +class Callable extends StmtParent, Member, @callable { + /** + * Gets the declared return type of this callable (`void` for + * constructors). + */ + Type getReturnType() { + constrs(this, _, _, result, _, _) or + methods(this, _, _, result, _, _) + } + + /** + * Gets a callee that may be called from this callable. + */ + Callable getACallee() { this.calls(result) } + + /** Gets the call site of a call from this callable to a callee. */ + Call getACallSite(Callable callee) { + result.getCaller() = this and + result.getCallee() = callee + } + + /** + * Gets the bytecode method descriptor, encoding parameter and return types, + * but not the name of the callable. + */ + string getMethodDescriptor() { + exists(string return | return = this.getReturnType().getTypeDescriptor() | + result = "(" + descriptorUpTo(this.getNumberOfParameters()) + ")" + return + ) + } + + private string descriptorUpTo(int n) { + (n = 0 and result = "") or + exists(Parameter p | p = this.getParameter(n-1) | + result = descriptorUpTo(n-1) + p.getType().getTypeDescriptor() + ) + } + + /** Holds if this callable calls `target`. */ + predicate calls(Callable target) { + exists(getACallSite(target)) + } + + /** + * Holds if this callable calls `target` + * using a `super(...)` constructor call. + */ + predicate callsSuperConstructor(Constructor target) { + getACallSite(target) instanceof SuperConstructorInvocationStmt + } + + /** + * Holds if this callable calls `target` + * using a `this(...)` constructor call. + */ + predicate callsThis(Constructor target) { + getACallSite(target) instanceof ThisConstructorInvocationStmt + } + + /** + * Holds if this callable calls `target` + * using a `super` method call. + */ + predicate callsSuper(Method target) { + getACallSite(target) instanceof SuperMethodAccess + } + + /** + * Holds if this callable calls `c` using + * either a `super(...)` constructor call + * or a `this(...)` constructor call. + */ + predicate callsConstructor(Constructor c) { + this.callsSuperConstructor(c) or this.callsThis(c) + } + + /** + * Holds if this callable may call the specified callable, + * taking overriding into account. + */ + predicate polyCalls(Callable m) { + this.calls(m) or + exists(Method mSuper, VirtualMethodAccess c | + c.getCaller() = this and c.getMethod() = mSuper + | + m.(Method).overrides(mSuper) + ) + } + + /** + * Holds if field `f` may be assigned a value + * within the body of this callable. + */ + predicate writes(Field f) { + f.getAnAccess().(LValue).getEnclosingCallable() = this + } + + /** + * Holds if field `f` may be read + * within the body of this callable. + */ + predicate reads(Field f) { + f.getAnAccess().(RValue).getEnclosingCallable() = this + } + + /** + * Holds if field `f` may be either read or written + * within the body of this callable. + */ + predicate accesses(Field f) { this.writes(f) or this.reads(f) } + + /** + * Gets a field accessed in this callable. + */ + Field getAnAccessedField() { this.accesses(result) } + + /** Gets the type of a formal parameter of this callable. */ + Type getAParamType() { result = getParameterType(_) } + + /** Holds if this callable does not have any formal parameters. */ + predicate hasNoParameters() { not exists(getAParameter()) } + + /** Gets the number of formal parameters of this callable. */ + int getNumberOfParameters() { + result = count(getAParameter()) + } + + /** Gets a formal parameter of this callable. */ + Parameter getAParameter() { result.getCallable() = this } + + /** Gets the formal parameter at the specified (zero-based) position. */ + Parameter getParameter(int n) { params(result, _, n, this, _) } + + /** Gets the type of the formal parameter at the specified (zero-based) position. */ + Type getParameterType(int n) { params(_, result, n, this, _) } + + /** + * Gets the signature of this callable, including its name and the types of all its parameters, + * identified by their simple (unqualified) names. + * + * Use `getSignature` to obtain a signature including fully qualified type names. + */ + string getStringSignature() { + result = this.getName() + this.paramsString() + } + + /** Gets a parenthesized string containing all parameter types of this callable, separated by a comma. */ + pragma[nomagic] + string paramsString() { + exists(int n | n = getNumberOfParameters() | + n = 0 and result = "()" + or + n > 0 and result = "(" + this.paramUpTo(n-1) + ")" + ) + } + + /** + * Gets a string containing the parameter types of this callable + * from left to right, up to (and including) the `n`-th parameter. + */ + private string paramUpTo(int n) { + n = 0 and result = getParameterType(0).toString() + or + n > 0 and result = paramUpTo(n-1) + ", " + getParameterType(n) + } + + /** Holds if this callable has the specified string signature. */ + predicate hasStringSignature(string sig) { + sig = this.getStringSignature() + } + + /** Gets an exception that occurs in the `throws` clause of this callable. */ + Exception getAnException() { exceptions(result,_,this) } + + /** Gets an exception type that occurs in the `throws` clause of this callable. */ + RefType getAThrownExceptionType() { result = getAnException().getType() } + + /** Gets a call site that references this callable. */ + Call getAReference() { + result.getCallee() = this + } + + /** Gets the body of this callable, if any. */ + Block getBody() { result.getParent() = this } + + /** + * Gets the source declaration of this callable. + * + * For parameterized instances of generic methods, the + * source declaration is the corresponding generic method. + * + * For non-parameterized callables declared inside a parameterized + * instance of a generic type, the source declaration is the + * corresponding callable in the generic type. + * + * For all other callables, the source declaration is the callable itself. + */ + Callable getSourceDeclaration() { result = this } + + /** Holds if this callable is the same as its source declaration. */ + predicate isSourceDeclaration() { this.getSourceDeclaration() = this } + + /** Cast this callable to a class that provides access to metrics information. */ + MetricCallable getMetrics() { result = this } + + /** Holds if the last parameter of this callable is a varargs (variable arity) parameter. */ + predicate isVarargs() { this.getAParameter().isVarargs() } + + /** + * Gets the signature of this callable, where all types in the signature have a fully-qualified name. + * + * For example, method `void m(String s)` has the signature `m(java.lang.String)`. + */ + string getSignature() { + constrs(this, _, result, _, _, _) or + methods(this, _, result, _, _, _) + } +} + +/** Holds if method `m1` overrides method `m2`. */ +private +predicate overrides(Method m1, Method m2) { + exists(RefType t1, RefType t2 | overridesIgnoringAccess(m1, t1, m2, t2) | + m2.isPublic() or + m2.isProtected() or + m2.isPackageProtected() and t1.getPackage() = t2.getPackage() + ) +} + +/** + * Auxiliary predicate: whether method `m1` overrides method `m2`, + * ignoring any access modifiers. Additionally, this predicate binds + * `t1` to the type declaring `m1` and `t2` to the type declaring `m2`. + */ +pragma[noopt] +predicate overridesIgnoringAccess(Method m1, RefType t1, Method m2, RefType t2) { + exists(string sig | + virtualMethodWithSignature(sig, t1, m1) and + t1.extendsOrImplements+(t2) and + virtualMethodWithSignature(sig, t2, m2) + ) +} + +private predicate virtualMethodWithSignature(string sig, RefType t, Method m) { + methods(m,_,_,_,t,_) and + sig = m.getSignature() and + m.isVirtual() +} + +private predicate potentialInterfaceImplementationWithSignature(string sig, RefType t, Method impl) { + t.hasMethod(impl, _) and + sig = impl.getSignature() and + impl.isVirtual() and + impl.isPublic() and + not t instanceof Interface and + not t.isAbstract() +} + +pragma[nomagic] +private predicate implementsInterfaceMethod(SrcMethod impl, SrcMethod m) { + exists(RefType t, Interface i, Method minst, Method implinst | + m = minst.getSourceDeclaration() and + i = minst.getDeclaringType() and + t.extendsOrImplements+(i) and + t.isSourceDeclaration() and + potentialInterfaceImplementationWithSignature(minst.getSignature(), t, implinst) and + impl = implinst.getSourceDeclaration() + ) +} + +/** A method is a particular kind of callable. */ +class Method extends Callable, @method { + /** Holds if this method (directly) overrides the specified callable. */ + predicate overrides(Method m) { overrides(this, m) } + + /** + * Holds if this method either overrides `m`, or `m` is the + * source declaration of this method (and not equal to it). + */ + predicate overridesOrInstantiates(Method m) { + this.overrides(m) or + this.getSourceDeclaration() = m and this != m + } + + /** Gets a method (directly or transitively) overridden by this method. */ + Method getAnOverride() { + this.overrides+(result) + } + + /** Gets the source declaration of a method overridden by this method. */ + SrcMethod getASourceOverriddenMethod() { + exists(Method m | this.overrides(m) and result = m.getSourceDeclaration()) + } + + override string getSignature() { methods(this,_,result,_,_,_) } + + /** + * Holds if this method and method `m` are declared in the same type + * and have the same parameter types. + */ + predicate sameParamTypes(Method m) { + // `this` and `m` are different methods, + this != m and + // `this` and `m` are declared in the same type, + this.getDeclaringType() = m.getDeclaringType() and + // `this` and `m` are of the same arity, and + this.getNumberOfParameters() = m.getNumberOfParameters() and + // there does not exist a pair of parameters whose types differ. + not exists(int n | this.getParameterType(n) != m.getParameterType(n)) + } + + override SrcMethod getSourceDeclaration() { methods(this,_,_,_,_,result) } + + /** + * All the methods that could possibly be called when this method + * is called. For class methods this includes the method itself and all its + * overriding methods (if any), and for interface methods this includes + * matching methods defined on or inherited by implementing classes. + * + * Only includes method implementations, not abstract or non-default interface methods. + * Native methods are included, since they have an implementation (just not in Java). + */ + SrcMethod getAPossibleImplementation() { + this.getSourceDeclaration().getAPossibleImplementationOfSrcMethod() = result + } + + override MethodAccess getAReference() { + result = Callable.super.getAReference() + } + + override predicate isPublic() { + Callable.super.isPublic() or + // JLS 9.4: Every method declaration in the body of an interface is implicitly public. + getDeclaringType() instanceof Interface or + exists(FunctionalExpr func | func.asMethod() = this) + } + + override predicate isAbstract() { + Callable.super.isAbstract() + or + // JLS 9.4: An interface method lacking a `default` modifier or a `static` modifier + // is implicitly abstract. + this.getDeclaringType() instanceof Interface and + not this.isDefault() and + not this.isStatic() + } + + override predicate isStrictfp() { + Callable.super.isStrictfp() or + // JLS 8.1.1.3, JLS 9.1.1.2 + getDeclaringType().isStrictfp() + } + + /** + * Holds if this method is neither private nor a static interface method + * nor an initializer method, and hence could be inherited. + */ + predicate isInheritable() { + not isPrivate() and + not (isStatic() and getDeclaringType() instanceof Interface) and + not this instanceof InitializerMethod + } + + /** + * Holds if this method is neither private nor static, and hence + * uses dynamic dispatch. + */ + predicate isVirtual() { + not isPrivate() and not isStatic() + } + + /** Holds if this method can be overridden. */ + predicate isOverridable() { + isVirtual() and + not isFinal() and + not getDeclaringType().isFinal() + } +} + +/** A method that is the same as its source declaration. */ +class SrcMethod extends Method { + SrcMethod() { methods(_,_,_,_,_,this) } + + /** + * All the methods that could possibly be called when this method + * is called. For class methods this includes the method itself and all its + * overriding methods (if any), and for interface methods this includes + * matching methods defined on or inherited by implementing classes. + * + * Only includes method implementations, not abstract or non-default interface methods. + * Native methods are included, since they have an implementation (just not in Java). + */ + SrcMethod getAPossibleImplementationOfSrcMethod() { + ( + if this.getDeclaringType() instanceof Interface and this.isVirtual() then + implementsInterfaceMethod(result, this) + else + result.getASourceOverriddenMethod*() = this + ) and + (exists(result.getBody()) or result.hasModifier("native")) + } +} + +/** + * A _setter_ method is a method with the following properties: + * + * - it has exactly one parameter, + * - its body contains exactly one statement + * that assigns the value of the method parameter to a field + * declared in the same type as the method. + */ +class SetterMethod extends Method { + SetterMethod() { + this.getNumberOfParameters() = 1 and + exists(ExprStmt s, Assignment a | + s = this.getBody().(SingletonBlock).getStmt() and a = s.getExpr() + | + exists(Field f | f.getDeclaringType() = this.getDeclaringType() | + a.getDest() = f.getAnAccess() and + a.getSource() = this.getAParameter().getAnAccess() + ) + ) + } + + /** Gets the field assigned by this setter method. */ + Field getField() { + exists(Assignment a | a.getEnclosingCallable() = this | + a.getDest() = result.getAnAccess() + ) + } +} + +/** + * A _getter_ method is a method with the following properties: + * + * - it has no parameters, + * - its body contains exactly one statement + * that returns the value of a field. + */ +class GetterMethod extends Method { + GetterMethod() { + this.hasNoParameters() and + exists(ReturnStmt s, Field f | s = this.getBody().(SingletonBlock).getStmt() | + s.getResult() = f.getAnAccess() + ) + } + + /** Gets the field whose value is returned by this getter method. */ + Field getField() { + exists(ReturnStmt r | r.getEnclosingCallable() = this | + r.getResult() = result.getAnAccess() + ) + } +} + +/** + * A finalizer method, with name `finalize`, + * return type `void` and modifier `protected`. + */ +class FinalizeMethod extends Method { + FinalizeMethod() { + this.hasName("finalize") and + this.getReturnType().hasName("void") and + this.isProtected() + } +} + +/** A constructor is a particular kind of callable. */ +class Constructor extends Callable, @constructor { + /** Holds if this is a default constructor, not explicitly declared in source code. */ + predicate isDefaultConstructor() { isDefConstr(this) } + + override Constructor getSourceDeclaration() { constrs(this,_,_,_,_,result) } + + override string getSignature() { constrs(this,_,result,_,_,_) } +} + +/** + * A compiler-generated initializer method (could be static or + * non-static), which is used to hold (static or non-static) field + * initializers, as well as explicit initializer blocks. + */ +abstract class InitializerMethod extends Method {} + +/** + * A static initializer is a method that contains all static + * field initializations and static initializer blocks. + */ +class StaticInitializer extends InitializerMethod { + StaticInitializer() { + hasName("") + } +} + +/** + * An instance initializer is a method that contains field initializations + * and explicit instance initializer blocks. + */ +class InstanceInitializer extends InitializerMethod { + InstanceInitializer() { + this.hasName("") + } +} + +/** A field declaration that declares one or more class or instance fields. */ +class FieldDeclaration extends ExprParent, @fielddecl, Annotatable { + /** Gets the access to the type of the field(s) in this declaration. */ + Expr getTypeAccess() { result.getParent() = this } + + /** Gets a field declared in this declaration. */ + Field getAField() { fieldDeclaredIn(result, this, _) } + + /** Gets the field declared at the specified (zero-based) position in this declaration */ + Field getField(int idx) { fieldDeclaredIn(result, this, idx) } + + /** Gets the number of fields declared in this declaration. */ + int getNumField() { result = max(int idx | fieldDeclaredIn(_, this, idx) | idx) + 1 } + + override string toString() { + if this.getNumField() = 0 then + result = this.getTypeAccess() + " " + this.getField(0) + ";" + else + result = this.getTypeAccess() + " " + this.getField(0) + ", ...;" + } +} + +/** A class or instance field. */ +class Field extends Member, ExprParent, @field, Variable { + /** Gets the declared type of this field. */ + override Type getType() { fields(this, _, result, _, _) } + + /** Gets the type in which this field is declared. */ + override RefType getDeclaringType() { fields(this, _, _, result, _) } + + /** + * Gets the field declaration in which this field is declared. + * + * Note that this declaration is only available if the field occurs in source code. + */ + FieldDeclaration getDeclaration() { result.getAField() = this } + + /** Gets the initializer expression of this field, if any. */ + override Expr getInitializer() { + exists(AssignExpr e, InitializerMethod im | + e.getDest() = this.getAnAccess() and + e.getSource() = result and + result.getEnclosingCallable() = im and + // This rules out updates in explicit initializer blocks as they are nested inside the compiler generated initializer blocks. + e.getEnclosingStmt().getParent() = im.getBody() + ) + } + + /** + * Gets the source declaration of this field. + * + * For fields that are members of a parameterized + * instance of a generic type, the source declaration is the + * corresponding field in the generic type. + * + * For all other fields, the source declaration is the field itself. + */ + Field getSourceDeclaration() { fields(this,_,_,_,result) } + + /** Holds if this field is the same as its source declaration. */ + predicate isSourceDeclaration() { this.getSourceDeclaration() = this } + + override predicate isPublic() { + Member.super.isPublic() or + // JLS 9.3: Every field declaration in the body of an interface is + // implicitly public, static, and final + getDeclaringType() instanceof Interface + } + + override predicate isStatic() { + Member.super.isStatic() or + // JLS 9.3: Every field declaration in the body of an interface is + // implicitly public, static, and final + this.getDeclaringType() instanceof Interface + } + + override predicate isFinal() { + Member.super.isFinal() or + // JLS 9.3: Every field declaration in the body of an interface is + // implicitly public, static, and final + this.getDeclaringType() instanceof Interface + } + + /** Cast this field to a class that provides access to metrics information. */ + MetricField getMetrics() { result = this } +} + +/** An instance field. */ +class InstanceField extends Field { + InstanceField() { + not this.isStatic() + } +} diff --git a/java/ql/src/semmle/code/java/Modifier.qll b/java/ql/src/semmle/code/java/Modifier.qll new file mode 100755 index 00000000000..73463369e6c --- /dev/null +++ b/java/ql/src/semmle/code/java/Modifier.qll @@ -0,0 +1,69 @@ +/** + * Provides classes and predicates for working with Java modifiers. + */ + +import Element + +/** A modifier such as `private`, `static` or `abstract`. */ +class Modifier extends Element, @modifier { + /** Gets the element to which this modifier applies. */ + Element getElement() { hasModifier(result,this) } +} + +/** An element of the Java syntax tree that may have a modifier. */ +abstract class Modifiable extends Element { + /** + * Holds if this element has modifier `m`. + * + * For most purposes, the more specialized predicates `isAbstract`, `isPublic`, etc. + * should be used, which also take implicit modifiers into account. + * For instance, non-default instance methods in interfaces are implicitly + * abstract, so `isAbstract()` will hold for them even if `hasModifier("abstract")` + * does not. + */ + predicate hasModifier(string m) { + modifiers(getAModifier(), m) + } + + /** Holds if this element has no modifier. */ + predicate hasNoModifier() { not hasModifier(this,_) } + + /** Gets a modifier of this element. */ + Modifier getAModifier() { this = result.getElement() } + + /** Holds if this element has an `abstract` modifier or is implicitly abstract. */ + predicate isAbstract() { hasModifier("abstract") } + + /** Holds if this element has a `static` modifier or is implicitly static. */ + predicate isStatic() { hasModifier("static") } + + /** Holds if this element has a `final` modifier or is implicitly final. */ + predicate isFinal() { hasModifier("final") } + + /** Holds if this element has a `public` modifier or is implicitly public. */ + predicate isPublic() { hasModifier("public") } + + /** Holds if this element has a `protected` modifier. */ + predicate isProtected() { hasModifier("protected") } + + /** Holds if this element has a `private` modifier or is implicitly private. */ + predicate isPrivate() { hasModifier("private") } + + /** Holds if this element has a `volatile` modifier. */ + predicate isVolatile() { hasModifier("volatile") } + + /** Holds if this element has a `synchronized` modifier. */ + predicate isSynchronized() { hasModifier("synchronized") } + + /** Holds if this element has a `native` modifier. */ + predicate isNative() { hasModifier("native") } + + /** Holds if this element has a `default` modifier. */ + predicate isDefault() { this.hasModifier("default") } + + /** Holds if this element has a `transient` modifier. */ + predicate isTransient() { this.hasModifier("transient") } + + /** Holds if this element has a `strictfp` modifier. */ + predicate isStrictfp() { this.hasModifier("strictfp") } +} diff --git a/java/ql/src/semmle/code/java/Modules.qll b/java/ql/src/semmle/code/java/Modules.qll new file mode 100755 index 00000000000..61973c3a22c --- /dev/null +++ b/java/ql/src/semmle/code/java/Modules.qll @@ -0,0 +1,211 @@ +/** + * Provides classes for working with Java modules. + */ + +import CompilationUnit + +/** + * A module. + */ +class Module extends @module { + Module() { + modules(this, _) + } + + /** + * Gets the name of this module. + */ + string getName() { modules(this, result) } + + /** + * Holds if this module is an `open` module, that is, + * it grants access _at run time_ to types in all its packages, + * as if all packages had been exported. + */ + predicate isOpen() { isOpen(this) } + + /** + * Gets a directive of this module. + */ + Directive getADirective() { directives(this, result) } + + /** + * Gets a compilation unit associated with this module. + */ + CompilationUnit getACompilationUnit() { cumodule(result, this) } + + /** Gets a textual representation of this module. */ + string toString() { modules(this, result) } +} + +/** + * A directive in a module declaration. + */ +abstract class Directive extends @directive { + /** Gets a textual representation of this directive. */ + abstract string toString(); +} + +/** + * A `requires` directive in a module declaration. + */ +class RequiresDirective extends Directive, @requires { + RequiresDirective() { + requires(this,_) + } + + /** + * Holds if this `requires` directive is `transitive`, + * that is, any module that depends on this module + * has an implicitly declared dependency on the + * module specified in this `requires` directive. + */ + predicate isTransitive() { isTransitive(this) } + + /** + * Holds if this `requires` directive is `static`, + * that is, the dependence specified by this `requires` + * directive is only mandatory at compile time but + * optional at run time. + */ + predicate isStatic() { isStatic(this) } + + /** + * Gets the module on which this module depends. + */ + Module getTargetModule() { requires(this, result) } + + override + string toString() { + exists(string transitive, string static | + (if isTransitive() then transitive = "transitive " else transitive = "") and + (if isStatic() then static = "static " else static = "") + | + result = "requires " + transitive + static + getTargetModule() + ";" + ) + } +} + +/** + * An `exports` directive in a module declaration. + */ +class ExportsDirective extends Directive, @exports { + ExportsDirective() { + exports(this,_) + } + + /** + * Gets the package exported by this `exports` directive. + */ + Package getExportedPackage() { exports(this, result) } + + /** + * Holds if this `exports` directive is qualified, that is, + * it contains a `to` clause. + * + * For qualified `exports` directives, exported types and members + * are accessible only to code in the specified modules. + * For unqualified `exports` directives, they are accessible + * to code in any module. + */ + predicate isQualified() { exportsTo(this, _) } + + /** + * Gets a module specified in the `to` clause of this + * `exports` directive, if any. + */ + Module getATargetModule() { exportsTo(this, result) } + + override + string toString() { + exists(string toClause | + if isQualified() then toClause = (" to " + concat(getATargetModule().getName(), ", ")) else toClause = "" + | + result = "exports " + getExportedPackage() + toClause + ";" + ) + } +} + +/** + * An `opens` directive in a module declaration. + */ +class OpensDirective extends Directive, @opens { + OpensDirective() { + opens(this,_) + } + + /** + * Gets the package opened by this `opens` directive. + */ + Package getOpenedPackage() { opens(this, result) } + + /** + * Holds if this `opens` directive is qualified, that is, + * it contains a `to` clause. + * + * For qualified `opens` directives, opened types and members + * are accessible only to code in the specified modules. + * For unqualified `opens` directives, they are accessible + * to code in any module. + */ + predicate isQualified() { opensTo(this, _) } + + /** + * Gets a module specified in the `to` clause of this + * `exports` directive, if any. + */ + Module getATargetModule() { opensTo(this, result) } + + override + string toString() { + exists(string toClause | + if isQualified() then toClause = (" to " + concat(getATargetModule().getName(), ", ")) else toClause = "" + | + result = "opens " + getOpenedPackage() + toClause + ";" + ) + } +} + +/** + * A `uses` directive in a module declaration. + */ +class UsesDirective extends Directive, @uses { + UsesDirective() { + uses(this,_) + } + + /** + * Gets the qualified name of the service interface specified in this `uses` directive. + */ + string getServiceInterfaceName() { uses(this, result) } + + override + string toString() { + result = "uses " + getServiceInterfaceName() + ";" + } +} + +/** + * A `provides` directive in a module declaration. + */ +class ProvidesDirective extends Directive, @provides { + ProvidesDirective() { + provides(this,_) + } + + /** + * Gets the qualified name of the service interface specified in this `provides` directive. + */ + string getServiceInterfaceName() { provides(this, result) } + + /** + * Gets the qualified name of a service implementation specified in this `provides` directive. + */ + string getServiceImplementationName() { providesWith(this, result) } + + override + string toString() { + result = "provides " + getServiceInterfaceName() + " with " + concat(getServiceImplementationName(), ", ") + ";" + } +} + diff --git a/java/ql/src/semmle/code/java/Package.qll b/java/ql/src/semmle/code/java/Package.qll new file mode 100755 index 00000000000..ae21a688d3b --- /dev/null +++ b/java/ql/src/semmle/code/java/Package.qll @@ -0,0 +1,34 @@ +/** + * Provides classes and predicates for working with Java packages. + */ + +import Element +import Type +import metrics.MetricPackage + +/** + * A package may be used to abstract over all of its members, + * regardless of which compilation unit they are defined in. + */ +class Package extends Element, Annotatable, @package { + /** Gets a top level type in this package. */ + TopLevelType getATopLevelType() { result.getPackage() = this } + + /** Holds if at least one reference type in this package originates from source code. */ + override predicate fromSource() { + exists(RefType t | t.fromSource() and t.getPackage() = this) + } + + /** Cast this package to a class that provides access to metrics information. */ + MetricPackage getMetrics() { result = this } + + /** + * A dummy URL for packages. + * + * This declaration is required to allow selection of packages in QL queries. + * Without it, an implicit call to `Package.getLocation()` would be generated + * when selecting a package, which would result in a compile-time error + * since packages do not have locations. + */ + string getURL() { result = "file://:0:0:0:0" } +} diff --git a/java/ql/src/semmle/code/java/Reflection.qll b/java/ql/src/semmle/code/java/Reflection.qll new file mode 100644 index 00000000000..700ca69dd1e --- /dev/null +++ b/java/ql/src/semmle/code/java/Reflection.qll @@ -0,0 +1,400 @@ +/** + * Provides classes and predicates for working with Java Reflection. + */ + +import java +import JDKAnnotations +import Serializability +import semmle.code.java.dataflow.DefUse + +predicate reflectivelyRead(Field f){ + f instanceof SerializableField or + f.getAnAnnotation() instanceof ReflectiveAccessAnnotation or + referencedInXmlFile(f) +} + +predicate reflectivelyWritten(Field f){ + f instanceof DeserializableField or + f.getAnAnnotation() instanceof ReflectiveAccessAnnotation or + referencedInXmlFile(f) +} + +/** + * Holds if a field's name and declaring type are referenced in an XML file. + * Usually, this implies that the field may be accessed reflectively. + */ +predicate referencedInXmlFile(Field f) { + elementReferencingField(f).getParent*() = elementReferencingType(f.getDeclaringType()) +} + +/** + * Gets an XML element with an attribute whose value is the name of `f`, + * suggesting that it might reference `f`. + */ +private XMLElement elementReferencingField(Field f) { + exists(elementReferencingType(f.getDeclaringType())) and + result.getAnAttribute().getValue() = f.getName() +} + +/** + * Gets an XML element with an attribute whose value is the fully qualified + * name of `rt`, suggesting that it might reference `rt`. + */ +private XMLElement elementReferencingType(RefType rt) { + result.getAnAttribute().getValue() = rt.getSourceDeclaration().getQualifiedName() +} + +private abstract class ReflectiveClassIdentifier extends Expr { + abstract RefType getReflectivelyIdentifiedClass(); +} + +private class ReflectiveClassIdentifierLiteral extends ReflectiveClassIdentifier, TypeLiteral { + override RefType getReflectivelyIdentifiedClass() { + result = getTypeName().getType().(RefType).getSourceDeclaration() + } +} + +/** + * A call to a Java standard library method which constructs or returns a `Class` from a `String`. + */ +library class ReflectiveClassIdentifierMethodAccess extends ReflectiveClassIdentifier, MethodAccess { + ReflectiveClassIdentifierMethodAccess() { + // A call to `Class.forName(...)`, from which we can infer `T` in the returned type `Class`. + getCallee().getDeclaringType() instanceof TypeClass and getCallee().hasName("forName") or + // A call to `ClassLoader.loadClass(...)`, from which we can infer `T` in the returned type `Class`. + getCallee().getDeclaringType().hasQualifiedName("java.lang", "ClassLoader") and getCallee().hasName("loadClass") + } + + /** + * If the argument to this call is a `StringLiteral`, then return that string. + */ + string getTypeName() { + result = getArgument(0).(StringLiteral).getRepresentedString() + } + + override RefType getReflectivelyIdentifiedClass() { + // We only handle cases where the class is specified as a string literal to this call. + result.getQualifiedName() = getTypeName() + } +} + +/** + * Gets a `ReflectiveClassIdentifier` that we believe may represent the value of `expr`. + */ +private ReflectiveClassIdentifier pointsToReflectiveClassIdentifier(Expr expr) { + // If this is an expression creating a `Class`, return the inferred `T` from the creation expression. + result = expr or + // Or if this is an access of a variable which was defined as an expression creating a `Class`, + // return the inferred `T` from the definition expression. + exists(RValue use, VariableAssign assign | + use = expr and + defUsePair(assign, use) and + // The source of the assignment must be a `ReflectiveClassIdentifier`. + result = assign.getSource() + ) +} + +/** + * Holds if `type` is considered to be "overly" generic. + */ +private predicate overlyGenericType(Type type) { + type instanceof TypeObject or + type instanceof TypeSerializable +} + +/** + * Identify "catch-all" bounded types, where the upper bound is an overly generic type, such as + * `? extends Object` and `? extends Serializable`. + */ +private predicate catchallType(BoundedType type) { + exists(Type upperBound | + upperBound = type.getUpperBoundType() + | + overlyGenericType(upperBound) + ) +} + +/** + * Given `Class` or `Constructor`, return all types `T`, such that + * `Class` or `Constructor` is, or is a sub-type of, `type`. + * + * In the case that `X` is a bounded type with an upper bound, and that upper bound is `Object` or + * `Serializable`, we return no sub-types. + */ +pragma[nomagic] +private Type parameterForSubTypes(ParameterizedType type) { + ( + type instanceof TypeClass or type instanceof TypeConstructor + ) and + // Only report "real" types. + not result instanceof TypeVariable and + // Identify which types the type argument `arg` could represent. + exists(Type arg | + arg = type.getTypeArgument(0) and + // Must not be a catch-all. + not catchallType(arg) + | + ( + // Simple case - this type is not a bounded type, so must represent exactly the `arg` class. + not arg instanceof BoundedType and result = arg + ) or + exists(RefType upperBound | + // Upper bound case + upperBound = arg.(BoundedType).getUpperBoundType() + | + /* + * `T extends Foo` implies that `Foo`, or any sub-type of `Foo`, may be represented. + */ + result.(RefType).getAnAncestor() = upperBound + ) or + exists(RefType lowerBound | + // Lower bound case + lowerBound = arg.(Wildcard).getLowerBoundType() + | + /* + * `T super Foo` implies that `Foo`, or any super-type of `Foo`, may be represented. + */ + lowerBound.(RefType).getAnAncestor() = result + ) + ) +} + +/** + * Given an expression whose type is `Class`, infer a possible set of types for `T`. + */ +Type inferClassParameterType(Expr expr) { + // Must be of type `Class` or `Class`. + expr.getType() instanceof TypeClass and + ( + /* + * If this `expr` is a `VarAccess` of a final or effectively final parameter, then look at the + * arguments to calls to this method, to see if we can infer anything from that case. + */ + exists(Parameter p | + p = expr.(VarAccess).getVariable() and + p.isEffectivelyFinal() + | + result = inferClassParameterType(p.getAnArgument()) + ) + or + if exists(pointsToReflectiveClassIdentifier(expr).getReflectivelyIdentifiedClass()) then + /* + * We've been able to identify where this `Class` instance was created, and identified the + * particular class that was loaded. + */ + result = pointsToReflectiveClassIdentifier(expr).getReflectivelyIdentifiedClass() + else + ( + /* + * If we haven't been able to find where the value for this expression was defined, then we + * resort to the type `T` in `Class`. + * + * If `T` refers to a bounded type with an upper bound, then we return all sub-types of the upper + * bound as possibilities for the instantiation, so long as this is not a catch-all type. + * + * A "catch-all" type is something like `? extends Object` or `? extends Serialization`, which + * would return too many sub-types. + */ + result = parameterForSubTypes(expr.getType()) + ) + ) +} + +/** + * Given an expression whose type is `Constructor`, infer a possible set of types for `T`. + */ +private Type inferConstructorParameterType(Expr expr) { + expr.getType() instanceof TypeConstructor and + // Return all the possible sub-types that could be instantiated. + // Not a catch-all `Constructor`, for example, `? extends Object` or `? extends Serializable`. + result = parameterForSubTypes(expr.getType()) +} + +/** + * Holds if a `Constructor.newInstance(...)` call for this type would expect an enclosing instance + * argument in the first position. + */ +private predicate expectsEnclosingInstance(RefType r) { + r instanceof NestedType and + not r.(NestedType).isStatic() +} + +/** + * A call to `Class.newInstance()` or `Constructor.newInstance()`. + */ +class NewInstance extends MethodAccess { + NewInstance() { + (getCallee().getDeclaringType() instanceof TypeClass or getCallee().getDeclaringType() instanceof TypeConstructor) and + getCallee().hasName("newInstance") + } + + /** + * Gets the `Constructor` that we believe will be invoked when this `newInstance()` method is + * called. + */ + Constructor getInferredConstructor() { + result = getInferredConstructedType().getAConstructor() and + if getCallee().getDeclaringType() instanceof TypeClass then + result.getNumberOfParameters() = 0 + else if getNumArgument() = 1 and getArgument(0).getType() instanceof Array then + /* + * This is a var-args array argument. If array argument is initialized inline, then identify + * the number of arguments specified in the array. + */ + if exists(getArgument(0).(ArrayCreationExpr).getInit()) then + // Count the number of elements in the initializer, and find the matching constructors. + matchConstructorArguments(result, count(getArgument(0).(ArrayCreationExpr).getInit().getAnInit())) + else + // Could be any of the constructors on this class. + any() + else + /* + * No var-args in play, just use the number of arguments to the `newInstance(..)` to determine + * which constructors may be called. + */ + matchConstructorArguments(result, getNumArgument()) + } + + /** + * Use the number of arguments to a `newInstance(..)` call to determine which constructor might be + * called. + * + * If the `Constructor` is for a non-static nested type, an extra argument is expected to be + * provided for the enclosing instance. + */ + private predicate matchConstructorArguments(Constructor c, int numArguments) { + if expectsEnclosingInstance(c.getDeclaringType()) then + c.getNumberOfParameters() = numArguments - 1 + else + c.getNumberOfParameters() = numArguments + } + + /** + * Gets an inferred type for the constructed class. + * + * To infer the constructed type we infer a type `T` for `Class` or `Constructor`, by inspecting + * points to results. + */ + RefType getInferredConstructedType() { + // Inferred type cannot be abstract. + not result.isAbstract() and + // `TypeVariable`s cannot be constructed themselves. + not result instanceof TypeVariable and + ( + // If this is called on a `Class` instance, return the inferred type `T`. + result = inferClassParameterType(getQualifier()) or + // If this is called on a `Constructor` instance, return the inferred type `T`. + result = inferConstructorParameterType(getQualifier()) or + // If the result of this is cast to a particular type, then use that type. + result = getCastInferredConstructedTypes() + ) + } + + /** + * If the result of this `newInstance` call is casted, infer the types that we could have + * constructed based on the cast. If the cast is to `Object` or `Serializable`, then we ignore the + * cast. + */ + private Type getCastInferredConstructedTypes() { + exists(CastExpr cast | + cast.getExpr() = this or cast.getExpr().(ParExpr).getExpr() = this + | + result = cast.getType() or + ( + /* + * If we cast the result of this method, then this is either the type specified, or a + * sub-type of that type. Make sure we exclude overly generic types such as `Object`. + */ + not overlyGenericType(cast.getType()) and + hasSubtype*(cast.getType(), result) + ) + ) + } +} + +/** + * A `MethodAccess` on a `Class` element. + */ +class ClassMethodAccess extends MethodAccess { + ClassMethodAccess() { + this.getCallee().getDeclaringType() instanceof TypeClass + } + + /** + * Gets an inferred type for the `Class` represented by this expression. + */ + RefType getInferredClassType() { + // `TypeVariable`s do not have methods themselves. + not result instanceof TypeVariable and + // If this is called on a `Class` instance, return the inferred type `T`. + result = inferClassParameterType(getQualifier()) + } +} + +/** + * A call to `Class.getMethod(..)` or `Class.getDeclaredMethod(..)`. + */ +class ReflectiveMethodAccess extends ClassMethodAccess { + ReflectiveMethodAccess() { + this.getCallee().hasName("getMethod") or + this.getCallee().hasName("getDeclaredMethod") + } + + /** + * Gets a `Method` that is inferred to be accessed by this reflective use of `getMethod(..)`. + */ + Method inferAccessedMethod() { + ( + if this.getCallee().hasName("getDeclaredMethod") then + // The method must be declared on the type itself. + result.getDeclaringType() = getInferredClassType() + else + // The method may be declared on an inferred type or a super-type. + getInferredClassType().inherits(result) + ) + and + // Only consider instances where the method name is provided as a `StringLiteral`. + result.hasName(getArgument(0).(StringLiteral).getRepresentedString()) + } +} + +/** + * A call to `Class.getAnnotation(..)`. + */ +class ReflectiveAnnotationAccess extends ClassMethodAccess { + ReflectiveAnnotationAccess() { + this.getCallee().hasName("getAnnotation") + } + + /** + * Gets a possible annotation type for this reflective annotation access. + */ + AnnotationType getAPossibleAnnotationType() { + result = inferClassParameterType(getArgument(0)) + } +} + +/** + * A call to `Class.getField(..)` that accesses a field. + */ +class ReflectiveFieldAccess extends ClassMethodAccess { + ReflectiveFieldAccess() { + this.getCallee().hasName("getField") or + this.getCallee().hasName("getDeclaredField") + } + + Field inferAccessedField() { + ( + if this.getCallee().hasName("getDeclaredField") then + // Declared fields must be on the type itself. + result.getDeclaringType() = getInferredClassType() + else + ( + // This field must be public, and be inherited by one of the inferred class types. + result.isPublic() and + getInferredClassType().inherits(result) + ) + ) and + result.hasName(getArgument(0).(StringLiteral).getRepresentedString()) + } +} diff --git a/java/ql/src/semmle/code/java/Serializability.qll b/java/ql/src/semmle/code/java/Serializability.qll new file mode 100644 index 00000000000..508ef67bad8 --- /dev/null +++ b/java/ql/src/semmle/code/java/Serializability.qll @@ -0,0 +1,33 @@ +/** + * Provides classes and predicates for working with Java Serialization. + */ + +import java +private import frameworks.jackson.JacksonSerializability +private import frameworks.google.GoogleHttpClientApi + +/** + * A serializable field may be read without code referencing it, + * due to the use of serialization. + */ +abstract class SerializableField extends Field { + +} +/** + * A deserializable field may be written without code referencing it, + * due to the use of serialization. + */ +abstract class DeserializableField extends Field { + +} + +/** + * A non-`transient` field in a type that (directly or indirectly) implements the `Serializable` interface + * and may be read or written via serialization. + */ +library class StandardSerializableField extends SerializableField, DeserializableField { + StandardSerializableField() { + this.getDeclaringType().getASupertype*() instanceof TypeSerializable and + not this.isTransient() + } +} diff --git a/java/ql/src/semmle/code/java/Statement.qll b/java/ql/src/semmle/code/java/Statement.qll new file mode 100755 index 00000000000..cc17f35e4eb --- /dev/null +++ b/java/ql/src/semmle/code/java/Statement.qll @@ -0,0 +1,825 @@ +/** + * Provides classes and predicates for working with Java statements. + */ + +import Expr +import metrics.MetricStmt + +/** A common super-class of all statements. */ +class Stmt extends StmtParent, ExprParent, @stmt { + /*abstract*/ override string toString() { result = "stmt" } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + string pp() { result = "stmt" } + + /** + * Gets the immediately enclosing callable (method or constructor) + * whose body contains this statement. + */ + Callable getEnclosingCallable() { stmts(this,_,_,_,result) } + + /** Gets the index of this statement as a child of its parent. */ + int getIndex() { stmts(this,_,_,result,_) } + + /** Gets the parent of this statement. */ + StmtParent getParent() { stmts(this,_,result,_,_) } + + /** Holds if this statement is the child of the specified parent at the specified (zero-based) position. */ + predicate isNthChildOf(StmtParent parent, int index) { + this.getParent() = parent and this.getIndex() = index + } + + /** Gets the compilation unit in which this statement occurs. */ + CompilationUnit getCompilationUnit() { result = this.getFile() } + + /** Gets a child of this statement, if any. */ + Stmt getAChild() { result.getParent() = this } + + /** Gets the basic block in which this statement occurs. */ + BasicBlock getBasicBlock() { result.getANode() = this } + + /** Gets the `ControlFlowNode` corresponding to this statement. */ + ControlFlowNode getControlFlowNode() { result = this } + + /** Cast this statement to a class that provides access to metrics information. */ + MetricStmt getMetrics() { result = this } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + string getHalsteadID() { result = "Stmt" } +} + +/** A statement parent is any element that can have a statement as its child. */ +class StmtParent extends @stmtparent, Top { +} + +/** A block of statements. */ +class Block extends Stmt,@block { + /** Gets a statement that is an immediate child of this block. */ + Stmt getAStmt() { result.getParent() = this } + + /** Gets the immediate child statement of this block that occurs at the specified (zero-based) position. */ + Stmt getStmt(int index) { result.getIndex() = index and result.getParent() = this } + + /** Gets the number of immediate child statements in this block. */ + int getNumStmt() { result = count(this.getAStmt()) } + + /** Gets the last statement in this block. */ + Stmt getLastStmt() { result = getStmt(getNumStmt()-1) } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { result = "{ ... }" } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "Block" } +} + +/** A block with only a single statement. */ +class SingletonBlock extends Block { + SingletonBlock() { this.getNumStmt() = 1 } + + /** Gets the single statement in this block. */ + Stmt getStmt() { result = getStmt(0) } +} + +/** + * A conditional statement, including `if`, `for`, + * `while` and `dowhile` statements. + */ +abstract class ConditionalStmt extends Stmt { + /** Gets the boolean condition of this conditional statement. */ + abstract Expr getCondition(); + + /** + * Gets the statement that is executed whenever the condition + * of this branch statement evaluates to `true`. + * + * DEPRECATED: use `ConditionNode.getATrueSuccessor()` instead. + */ + deprecated abstract Stmt getTrueSuccessor(); +} + +/** An `if` statement. */ +class IfStmt extends ConditionalStmt,@ifstmt { + /** Gets the boolean condition of this `if` statement. */ + override Expr getCondition() { result.isNthChildOf(this, 0) } + + /** Gets the `then` branch of this `if` statement. */ + Stmt getThen() { result.isNthChildOf(this, 1) } + + /** + * Gets the statement that is executed whenever the condition + * of this branch statement evaluates to `true`. + */ + override Stmt getTrueSuccessor() { result = getThen() } + + /** Gets the `else` branch of this `if` statement. */ + Stmt getElse() { result.isNthChildOf(this, 2) } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + result = "if (...) " + this.getThen().pp() + " else " + this.getElse().pp() + or + (not exists(this.getElse()) and result = "if (...) " + this.getThen().pp()) + } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "IfStmt" } +} + +/** A `for` loop. */ +class ForStmt extends ConditionalStmt,@forstmt { + /** + * Gets an initializer expression of the loop. + * + * This may be an assignment expression or a + * local variable declaration expression. + */ + Expr getAnInit() { + exists(int index | result.isNthChildOf(this, index) | index <= -1) + } + + /** Gets the initializer expression of the loop at the specified (zero-based) position. */ + Expr getInit(int index) { + result = getAnInit() and + index = -1 - result.getIndex() + } + + /** Gets the boolean condition of this `for` loop. */ + override Expr getCondition() { result.isNthChildOf(this, 1) } + + /** Gets an update expression of this `for` loop. */ + Expr getAnUpdate() { + exists(int index | result.isNthChildOf(this, index) | index >= 3) + } + + /** Gets the update expression of this loop at the specified (zero-based) position. */ + Expr getUpdate(int index) { + result = getAnUpdate() and + index = result.getIndex() - 3 + } + + /** Gets the body of this `for` loop. */ + Stmt getStmt() { result.getParent() = this and result.getIndex() = 2 } + + /** + * Gets the statement that is executed whenever the condition + * of this branch statement evaluates to true. + */ + override Stmt getTrueSuccessor() { result = getStmt() } + + /** + * Gets a variable that is used as an iteration variable: it is defined, + * updated or tested in the head of the `for` statement. + * + * This only returns variables that are quite certainly loop variables; + * for complex iterations, it may not return anything. + * + * More precisely, it returns variables that are both accessed in the + * condition of this `for` statement and updated in the update expression + * of this for statement but may be initialized elsewhere. + */ + Variable getAnIterationVariable() { + // Check that the variable is assigned to, incremented or decremented in the update expression, and... + exists(Expr update | update = getAnUpdate().getAChildExpr*() | + update.(UnaryAssignExpr).getExpr() = result.getAnAccess() or + update = result.getAnAssignedValue() + ) and + // ...that it is checked or used in the condition. + getCondition().getAChildExpr*() = result.getAnAccess() + } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + result = "for (...;...;...) " + this.getStmt().pp() + } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "ForStmt" } +} + +/** An enhanced `for` loop. (Introduced in Java 5.) */ +class EnhancedForStmt extends Stmt,@enhancedforstmt { + /** Gets the local variable declaration expression of this enhanced `for` loop. */ + LocalVariableDeclExpr getVariable() { result.getParent() = this } + + /** Gets the expression over which this enhanced `for` loop iterates. */ + Expr getExpr() { result.isNthChildOf(this, 1) } + + /** Gets the body of this enhanced `for` loop. */ + Stmt getStmt() { result.getParent() = this } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + result = "for (...) " + this.getStmt().pp() + } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "EnhancedForStmt" } +} + +/** A `while` loop. */ +class WhileStmt extends ConditionalStmt,@whilestmt { + /** Gets the boolean condition of this `while` loop. */ + override Expr getCondition() { result.getParent() = this } + + /** Gets the body of this `while` loop. */ + Stmt getStmt() { result.getParent() = this } + + /** + * Gets the statement that is executed whenever the condition + * of this branch statement evaluates to true. + */ + override Stmt getTrueSuccessor() { result = getStmt() } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + result = "while (...) " + this.getStmt().pp() + } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "WhileStmt" } +} + +/** A `do` loop. */ +class DoStmt extends ConditionalStmt,@dostmt { + /** Gets the condition of this `do` loop. */ + override Expr getCondition() { result.getParent() = this } + + /** Gets the body of this `do` loop. */ + Stmt getStmt() { result.getParent() = this } + + /** + * Gets the statement that is executed whenever the condition + * of this branch statement evaluates to `true`. + */ + override Stmt getTrueSuccessor() { result = getStmt() } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + result = "do " + this.getStmt().pp() + " while (...)" + } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "DoStmt" } +} + +/** + * A loop statement, including `for`, enhanced `for`, + * `while` and `do` statements. + */ +class LoopStmt extends Stmt { + LoopStmt() { + this instanceof ForStmt or + this instanceof EnhancedForStmt or + this instanceof WhileStmt or + this instanceof DoStmt + } + + /** Gets the body of this loop statement. */ + Stmt getBody() { + result = this.(ForStmt).getStmt() or + result = this.(EnhancedForStmt).getStmt() or + result = this.(WhileStmt).getStmt() or + result = this.(DoStmt).getStmt() + } + + /** Gets the boolean condition of this loop statement. */ + Expr getCondition() { + result = this.(ForStmt).getCondition() or + result = this.(WhileStmt).getCondition() or + result = this.(DoStmt).getCondition() + } +} + +/** A `try` statement. */ +class TryStmt extends Stmt,@trystmt { + /** Gets the block of the `try` statement. */ + Stmt getBlock() { result.isNthChildOf(this, -1) } + + /** Gets a `catch` clause of this `try` statement. */ + CatchClause getACatchClause() { result.getParent() = this } + + /** + * Gets the `catch` clause at the specified (zero-based) position + * in this `try` statement. + */ + CatchClause getCatchClause(int index) { + result = this.getACatchClause() and + result.getIndex() = index + } + + /** Gets the `finally` block, if any. */ + Block getFinally() { result.isNthChildOf(this, -2) } + + /** Gets a resource variable declaration, if any. */ + LocalVariableDeclStmt getAResourceDecl() { + result.getParent() = this and result.getIndex() <= -3 + } + + /** Gets the resource variable declaration at the specified position in this `try` statement. */ + LocalVariableDeclStmt getResourceDecl(int index) { + result = this.getAResourceDecl() and + index = -3 - result.getIndex() + } + + /** Gets a resource expression, if any. */ + VarAccess getAResourceExpr() { + result.getParent() = this and result.getIndex() <= -3 + } + + /** Gets the resource expression at the specified position in this `try` statement. */ + VarAccess getResourceExpr(int index) { + result = this.getAResourceExpr() and + index = -3 - result.getIndex() + } + + /** Gets a resource in this `try` statement, if any. */ + ExprParent getAResource() { + result = getAResourceDecl() or result = getAResourceExpr() + } + + /** Gets the resource at the specified position in this `try` statement. */ + ExprParent getResource(int index) { + result = getResourceDecl(index) or result = getResourceExpr(index) + } + + /** Gets a resource variable, if any, either from a resource variable declaration or resource expression. */ + Variable getAResourceVariable() { + result = getAResourceDecl().getAVariable().getVariable() or + result = getAResourceExpr().getVariable() + } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + result = "try " + this.getBlock().pp() + " catch (...)" + } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "TryStmt" } +} + +/** A `catch` clause in a `try` statement. */ +class CatchClause extends Stmt,@catchclause { + /** Gets the block of this `catch` clause. */ + Block getBlock() { result.getParent() = this } + + /** Gets the `try` statement in which this `catch` clause occurs. */ + TryStmt getTry() { this = result.getACatchClause() } + + /** Gets the parameter of this `catch` clause. */ + LocalVariableDeclExpr getVariable() { result.getParent() = this } + + /** Holds if this `catch` clause is a _multi_-`catch` clause. */ + predicate isMultiCatch() { + this.getVariable().getTypeAccess() instanceof UnionTypeAccess + } + + /** Gets a type caught by this `catch` clause. */ + RefType getACaughtType() { + exists(Expr ta | ta = getVariable().getTypeAccess() | + result = ta.(TypeAccess).getType() or + result = ta.(UnionTypeAccess).getAnAlternative().getType() + ) + } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + result = "catch (...) " + this.getBlock().pp() + } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "CatchClause" } +} + +/** A `switch` statement. */ +class SwitchStmt extends Stmt,@switchstmt { + /** Gets an immediate child statement of this `switch` statement. */ + Stmt getAStmt() { result.getParent() = this } + + /** + * Gets the immediate child statement of this `switch` statement + * that occurs at the specified (zero-based) position. + */ + Stmt getStmt(int index) { result = this.getAStmt() and result.getIndex() = index } + + /** + * Gets a case of this `switch` statement, + * which may be either a normal `case` or a `default`. + */ + SwitchCase getACase() { result = getAConstCase() or result = getDefaultCase() } + + /** Gets a (non-default) `case` of this `switch` statement. */ + ConstCase getAConstCase() { result.getParent() = this } + + /** Gets the `default` case of this switch statement, if any. */ + DefaultCase getDefaultCase() { result.getParent() = this } + + /** Gets the expression of this `switch` statement. */ + Expr getExpr() { result.getParent() = this } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + result = "switch (...)" + } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "SwitchStmt" } +} + +/** + * A case of a `switch` statement. + * + * This includes both normal `case`s and the `default` case. + */ +class SwitchCase extends Stmt, @case { + SwitchStmt getSwitch() { result.getACase() = this } +} + +/** A constant `case` of a switch statement. */ +class ConstCase extends SwitchCase { + ConstCase() { exists(Expr e | e.getParent() = this) } + + /** Gets the expression of this `case`. */ + Expr getValue() { result.getParent() = this } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + result = "case ...:" + } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "ConstCase" } +} + +/** A `default` case of a `switch` statement */ +class DefaultCase extends SwitchCase { + DefaultCase() { not exists(Expr e | e.getParent() = this) } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + result = "default" + } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "DefaultCase" } +} + +/** A `synchronized` statement. */ +class SynchronizedStmt extends Stmt,@synchronizedstmt { + /** Gets the expression on which this `synchronized` statement synchronizes. */ + Expr getExpr() { result.getParent() = this } + + /** Gets the block of this `synchronized` statement. */ + Stmt getBlock() { result.getParent() = this } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + result = "synchronized (...) " + this.getBlock().pp() + } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "SynchronizedStmt" } +} + +/** A `return` statement. */ +class ReturnStmt extends Stmt,@returnstmt { + /** Gets the expression returned by this `return` statement, if any. */ + Expr getResult() { result.getParent() = this } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + result = "return ..." + } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "ReturnStmt" } +} + +/** A `throw` statement. */ +class ThrowStmt extends Stmt,@throwstmt { + /** Gets the expression thrown by this `throw` statement. */ + Expr getExpr() { result.getParent() = this } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + result = "throw ..." + } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "ThrowStmt" } + + /** Gets the type of the expression thrown by this `throw` statement. */ + RefType getThrownExceptionType() { result = getExpr().getType() } + + /** + * Gets the `catch` clause that catches the exception + * thrown by this `throws` statement and occurs + * in the same method as this `throws` statement, + * provided such a `catch` exists. + */ + CatchClause getLexicalCatchIfAny() { + exists(TryStmt try | try = findEnclosing() and result = catchClauseForThis(try)) + } + + private Stmt findEnclosing() { + result = getParent() or + exists(Stmt mid | + mid = findEnclosing() and + not exists(this.catchClauseForThis((TryStmt)mid)) and + result = mid.getParent() + ) + } + + private CatchClause catchClauseForThis(TryStmt try) { + result = try.getACatchClause() and + result.getEnclosingCallable() = this.getEnclosingCallable() and + ((RefType)getExpr().getType()).hasSupertype*((RefType)result.getVariable().getType()) and + not this.getParent+() = result + } +} + +/** A `break` or `continue` statement. */ +class JumpStmt extends Stmt { + JumpStmt() { + this instanceof BreakStmt or + this instanceof ContinueStmt + } + + /** + * Gets the labeled statement that this `break` or + * `continue` statement refers to, if any. + */ + LabeledStmt getTargetLabel() { + this.getParent+() = result and + namestrings(result.getLabel(), _, this) + } + + private Stmt getLabelTarget() { + result = getTargetLabel().getStmt() + } + + private Stmt getAPotentialTarget() { + this.getParent+() = result and + ( + result instanceof LoopStmt or + this instanceof BreakStmt and result instanceof SwitchStmt + ) + } + + private Stmt getEnclosingTarget() { + result = getAPotentialTarget() and + not exists(Stmt other | other = getAPotentialTarget() | other.getParent+() = result) + } + + /** + * Gets the statement that this `break` or `continue` jumps to. + */ + Stmt getTarget() { + result = getLabelTarget() or + (not exists(getLabelTarget()) and result = getEnclosingTarget()) + } +} + +/** A `break` statement. */ +class BreakStmt extends Stmt,@breakstmt { + /** Gets the label targeted by this `break` statement, if any. */ + string getLabel() { namestrings(result,_,this) } + + /** Holds if this `break` statement has an explicit label. */ + predicate hasLabel() { exists(string s | s = this.getLabel()) } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + if this.hasLabel() then + result = "break " + this.getLabel() + else + result = "break" + } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "BreakStmt" } +} + +/** A `continue` statement. */ +class ContinueStmt extends Stmt,@continuestmt { + /** Gets the label targeted by this `continue` statement, if any. */ + string getLabel() { namestrings(result,_,this) } + + /** Holds if this `continue` statement has an explicit label. */ + predicate hasLabel() { exists(string s | s = this.getLabel()) } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + if this.hasLabel() then + result = "continue " + this.getLabel() + else + result = "continue" + } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "ContinueStmt" } +} + +/** The empty statement. */ +class EmptyStmt extends Stmt,@emptystmt { + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + result = ";" + } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "EmptyStmt" } +} + +/** + * An expression statement. + * + * Certain kinds of expressions may be used as statements by appending a semicolon. + */ +class ExprStmt extends Stmt,@exprstmt { + /** Gets the expression of this expression statement. */ + Expr getExpr() { result.getParent() = this } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + result = "...;" + } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "ExprStmt" } + + /** Holds if this statement represents a field declaration with an initializer. */ + predicate isFieldDecl() { + getEnclosingCallable() instanceof InitializerMethod and + exists(FieldDeclaration fd, Location fdl, Location sl | + fdl = fd.getLocation() and sl = getLocation() + | + fdl.getFile() = sl.getFile() and + fdl.getStartLine() = sl.getStartLine() and + fdl.getStartColumn() = sl.getStartColumn() + ) + } +} + +/** A labeled statement. */ +class LabeledStmt extends Stmt,@labeledstmt { + /** Gets the statement of this labeled statement. */ + Stmt getStmt() { result.getParent() = this } + + /** Gets the label of this labeled statement. */ + string getLabel() { namestrings(result,_,this) } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + result = this.getLabel() + ": " + this.getStmt().pp() + } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = this.getLabel() + ":" } +} + +/** An `assert` statement. */ +class AssertStmt extends Stmt,@assertstmt { + /** Gets the boolean expression of this `assert` statement. */ + Expr getExpr() { exprs(result,_,_,this,_) and result.getIndex() = 0 } + + /** Gets the assertion message expression, if any. */ + Expr getMessage() { exprs(result,_,_,this,_) and result.getIndex() = 1 } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + if exists(this.getMessage()) then + result = "assert ... : ..." + else + result = "assert ..." + } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "AssertStmt" } +} + +/** A statement that declares one or more local variables. */ +class LocalVariableDeclStmt extends Stmt,@localvariabledeclstmt { + /** Gets a declared variable. */ + LocalVariableDeclExpr getAVariable() { result.getParent() = this } + + /** Gets the variable declared at the specified (one-based) position in this local variable declaration statement. */ + LocalVariableDeclExpr getVariable(int index) { + result = this.getAVariable() and + result.getIndex() = index + } + + /** Gets an index of a variable declared in this local variable declaration statement. */ + int getAVariableIndex() { + exists(getVariable(result)) + } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + result = "local variable declaration" + } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "LocalVariableDeclStmt" } +} + +/** A statement that declares a local class. */ +class LocalClassDeclStmt extends Stmt,@localclassdeclstmt { + /** Gets the local class declared by this statement. */ + LocalClass getLocalClass() { isLocalClass(result,this) } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + result = "local class declaration: " + this.getLocalClass().toString() + } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "LocalClassDeclStmt" } +} + +/** An explicit `this(...)` constructor invocation. */ +class ThisConstructorInvocationStmt extends Stmt, ConstructorCall, @constructorinvocationstmt { + /** Gets an argument of this constructor invocation. */ + override Expr getAnArgument() { result.getIndex() >= 0 and result.getParent() = this } + + /** Gets the argument at the specified (zero-based) position in this constructor invocation. */ + override Expr getArgument(int index) { + result = this.getAnArgument() and + result.getIndex() = index + } + + /** Gets a type argument of this constructor invocation. */ + Expr getATypeArgument() { result.getIndex() <= -2 and result.getParent() = this } + + /** Gets the type argument at the specified (zero-based) position in this constructor invocation. */ + Expr getTypeArgument(int index) { + result = this.getATypeArgument() and + (-2 - result.getIndex()) = index + } + + /** Gets the constructor invoked by this constructor invocation. */ + override Constructor getConstructor() { callableBinding(this,result) } + + override Expr getQualifier() { none() } + + /** Gets the immediately enclosing callable of this constructor invocation. */ + override Callable getEnclosingCallable() { result = Stmt.super.getEnclosingCallable() } + + /** Gets the immediately enclosing statement of this constructor invocation. */ + override Stmt getEnclosingStmt() { result = this } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + result = "this(...)" + } + + /** Gets a printable representation of this statement. */ + override string toString() { result = pp() } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "ConstructorInvocationStmt" } +} + +/** An explicit `super(...)` constructor invocation. */ +class SuperConstructorInvocationStmt extends Stmt, ConstructorCall, @superconstructorinvocationstmt { + /** Gets an argument of this constructor invocation. */ + override Expr getAnArgument() { result.getIndex() >= 0 and result.getParent() = this } + + /** Gets the argument at the specified (zero-based) position in this constructor invocation. */ + override Expr getArgument(int index) { + result = this.getAnArgument() and + result.getIndex() = index + } + + /** Gets a type argument of this constructor invocation. */ + Expr getATypeArgument() { result.getIndex() <= -2 and result.getParent() = this } + + /** Gets the type argument at the specified (zero-based) position in this constructor invocation. */ + Expr getTypeArgument(int index) { + result = this.getATypeArgument() and + (-2 - result.getIndex()) = index + } + + /** Gets the constructor invoked by this constructor invocation. */ + override Constructor getConstructor() { callableBinding(this,result) } + + /** Gets the qualifier expression of this `super(...)` constructor invocation, if any. */ + override Expr getQualifier() { result.isNthChildOf(this, -1) } + + /** Gets the immediately enclosing callable of this constructor invocation. */ + override Callable getEnclosingCallable() { result = Stmt.super.getEnclosingCallable() } + + /** Gets the immediately enclosing statement of this constructor invocation. */ + override Stmt getEnclosingStmt() { result = this } + + /** Gets a printable representation of this statement. May include more detail than `toString()`. */ + override string pp() { + result = "super(...)" + } + + /** Gets a printable representation of this statement. */ + override string toString() { result = pp() } + + /** This statement's Halstead ID (used to compute Halstead metrics). */ + override string getHalsteadID() { result = "SuperConstructorInvocationStmt" } +} diff --git a/java/ql/src/semmle/code/java/StringFormat.qll b/java/ql/src/semmle/code/java/StringFormat.qll new file mode 100644 index 00000000000..18ff3307b2a --- /dev/null +++ b/java/ql/src/semmle/code/java/StringFormat.qll @@ -0,0 +1,422 @@ +import java +import dataflow.DefUse + +/** + * A library method that formats a number of its arguments according to a + * format string. + */ +private abstract class FormatMethod extends Method { + /** Gets the index of the format string argument. */ + abstract int getFormatStringIndex(); +} + +/** + * A library method that acts like `String.format` by formatting a number of + * its arguments according to a format string. + */ +class StringFormatMethod extends FormatMethod { + StringFormatMethod() { + ( + this.hasName("format") or + this.hasName("printf") or + this.hasName("readLine") or + this.hasName("readPassword") + ) and ( + this.getDeclaringType().hasQualifiedName("java.lang", "String") or + this.getDeclaringType().hasQualifiedName("java.io", "PrintStream") or + this.getDeclaringType().hasQualifiedName("java.io", "PrintWriter") or + this.getDeclaringType().hasQualifiedName("java.io", "Console") or + this.getDeclaringType().hasQualifiedName("java.util", "Formatter") + ) + } + + override int getFormatStringIndex() { + result = 0 and this.getSignature() = "format(java.lang.String,java.lang.Object[])" or + result = 0 and this.getSignature() = "printf(java.lang.String,java.lang.Object[])" or + result = 1 and this.getSignature() = "format(java.util.Locale,java.lang.String,java.lang.Object[])" or + result = 1 and this.getSignature() = "printf(java.util.Locale,java.lang.String,java.lang.Object[])" or + result = 0 and this.getSignature() = "readLine(java.lang.String,java.lang.Object[])" or + result = 0 and this.getSignature() = "readPassword(java.lang.String,java.lang.Object[])" + } +} + +/** + * A format method using the `org.slf4j.Logger` format string syntax. That is, + * the placeholder string is `"{}"`. + */ +class LoggerFormatMethod extends FormatMethod { + LoggerFormatMethod() { + ( + this.hasName("debug") or + this.hasName("error") or + this.hasName("info") or + this.hasName("trace") or + this.hasName("warn") + ) and + this.getDeclaringType().getASourceSupertype*().hasQualifiedName("org.slf4j", "Logger") + } + + override int getFormatStringIndex() { + (result = 0 or result = 1) and + this.getParameterType(result) instanceof TypeString + } +} + +private newtype TFmtSyntax = TFmtPrintf() or TFmtLogger() + +/** A syntax for format strings. */ +class FmtSyntax extends TFmtSyntax { + string toString() { + result = "printf (%) syntax" and this = TFmtPrintf() or + result = "logger ({}) syntax" and this = TFmtLogger() + } +} + +/** + * Holds if `c` wraps a call to a `StringFormatMethod`, such that `fmtix` is + * the index of the format string argument to `c` and the following and final + * argument is the `Object[]` that holds the arguments to be formatted. + */ +private predicate formatWrapper(Callable c, int fmtix, FmtSyntax syntax) { + exists(Parameter fmt, Parameter args, Call fmtcall, int i | + fmt = c.getParameter(fmtix) and + fmt.getType() instanceof TypeString and + args = c.getParameter(fmtix+1) and + args.getType().(Array).getElementType() instanceof TypeObject and + c.getNumberOfParameters() = fmtix+2 and + fmtcall.getEnclosingCallable() = c and + ( + formatWrapper(fmtcall.getCallee(), i, syntax) or + fmtcall.getCallee().(StringFormatMethod).getFormatStringIndex() = i and syntax = TFmtPrintf() or + fmtcall.getCallee().(LoggerFormatMethod).getFormatStringIndex() = i and syntax = TFmtLogger() + ) and + fmtcall.getArgument(i) = fmt.getAnAccess() and + fmtcall.getArgument(i+1) = args.getAnAccess() + ) +} + +/** + * A call to a `StringFormatMethod` or a callable wrapping a `StringFormatMethod`. + */ +class FormattingCall extends Call { + FormattingCall() { + this.getCallee() instanceof FormatMethod or + formatWrapper(this.getCallee(), _, _) + } + + /** Gets the index of the format string argument. */ + private int getFormatStringIndex() { + this.getCallee().(FormatMethod).getFormatStringIndex() = result or + formatWrapper(this.getCallee(), result, _) + } + + FmtSyntax getSyntax() { + this.getCallee() instanceof StringFormatMethod and result = TFmtPrintf() or + this.getCallee() instanceof LoggerFormatMethod and result = TFmtLogger() or + formatWrapper(this.getCallee(), _, result) + } + + private Expr getLastArg() { + exists(Expr last | + last = this.getArgument(this.getNumArgument() - 1) + | + if this.hasExplicitVarargsArray() then + result = last.(ArrayCreationExpr).getInit().getInit(getVarargsCount() - 1) + else + result = last + ) + } + + predicate hasTrailingThrowableArgument() { + getSyntax() = TFmtLogger() and + getLastArg().getType().(RefType).getASourceSupertype*() instanceof TypeThrowable + } + + /** Gets the argument to this call in the position of the format string */ + Expr getFormatArgument() { + result = this.getArgument(this.getFormatStringIndex()) + } + + /** Gets an argument to be formatted. */ + Expr getAnArgumentToBeFormatted() { + exists(int i | + result = this.getArgument(i) and + i > this.getFormatStringIndex() and + not hasExplicitVarargsArray() + ) + } + + /** Holds if the varargs argument is given as an explicit array. */ + private predicate hasExplicitVarargsArray() { + this.getNumArgument() = this.getFormatStringIndex() + 2 and + this.getArgument(1 + this.getFormatStringIndex()).getType() instanceof Array + } + + /** Gets the length of the varargs array if it can determined. */ + int getVarargsCount() { + if this.hasExplicitVarargsArray() then + exists(Expr arg | arg = this.getArgument(1 + this.getFormatStringIndex()) | + result = arg.(ArrayCreationExpr).getFirstDimensionSize() or + result = arg.(VarAccess).getVariable().getAnAssignedValue().(ArrayCreationExpr).getFirstDimensionSize() + ) + else + result = this.getNumArgument() - this.getFormatStringIndex() - 1 + } + + /** Gets a `FormatString` that is used by this call. */ + FormatString getAFormatString() { + result.getAFormattingUse() = this + } +} + +/** Holds if `m` calls `toString()` on its `i`th argument. */ +private predicate printMethod(Method m, int i) { + exists(RefType t | + t = m.getDeclaringType() and + m.getParameterType(i) instanceof TypeObject + | + (t.hasQualifiedName("java.io", "PrintWriter") or t.hasQualifiedName("java.io", "PrintStream")) and + (m.hasName("print") or m.hasName("println")) + or + (t.hasQualifiedName("java.lang", "StringBuilder") or t.hasQualifiedName("java.lang", "StringBuffer")) and + (m.hasName("append") or m.hasName("insert")) + or + t instanceof TypeString and m.hasName("valueOf") + ) +} + +/** + * Holds if `e` occurs in a position where it may be converted to a string by + * an implicit call to `toString()`. + */ +predicate implicitToStringCall(Expr e) { + not e.getType() instanceof TypeString and + ( + exists(FormattingCall fmtcall | fmtcall.getAnArgumentToBeFormatted() = e) or + exists(AddExpr add | add.getType() instanceof TypeString and add.getAnOperand() = e) or + exists(MethodAccess ma, Method m, int i | + ma.getMethod() = m and + ma.getArgument(i) = e and + printMethod(m, i) + ) + ) +} + +/** + * A call to a `format` or `printf` method. + */ +class StringFormat extends MethodAccess, FormattingCall { + StringFormat() { + this.getCallee() instanceof StringFormatMethod + } +} + +/** + * Holds if `fmt` is used as part of a format string. + */ +private predicate formatStringFragment(Expr fmt) { + any(FormattingCall call).getFormatArgument() = fmt or + exists(Expr e | formatStringFragment(e) | + e.(VarAccess).getVariable().getAnAssignedValue() = fmt or + e.(AddExpr).getLeftOperand() = fmt or + e.(AddExpr).getRightOperand() = fmt or + e.(ConditionalExpr).getTrueExpr() = fmt or + e.(ConditionalExpr).getFalseExpr() = fmt or + e.(ParExpr).getExpr() = fmt + ) +} + +/** + * Holds if `e` is a part of a format string with the approximate value + * `fmtvalue`. The value is approximated by ignoring details that are + * irrelevant for determining the number of format specifiers in the resulting + * string. + */ +private predicate formatStringValue(Expr e, string fmtvalue) { + formatStringFragment(e) and + ( + e.(StringLiteral).getRepresentedString() = fmtvalue or + e.getType() instanceof IntegralType and fmtvalue = "1" or // dummy value + e.getType() instanceof BooleanType and fmtvalue = "x" or // dummy value + e.getType() instanceof EnumType and fmtvalue = "x" or // dummy value + formatStringValue(e.(ParExpr).getExpr(), fmtvalue) or + exists(Variable v | + e = v.getAnAccess() and + v.isFinal() and + v.getType() instanceof TypeString and + formatStringValue(v.getInitializer(), fmtvalue) + ) or + exists(LocalVariableDecl v | + e = v.getAnAccess() and + not exists(AssignAddExpr aa | aa.getDest() = v.getAnAccess()) and + 1 = count(v.getAnAssignedValue()) and + v.getType() instanceof TypeString and + formatStringValue(v.getAnAssignedValue(), fmtvalue) + ) or + exists(AddExpr add, string left, string right | + add = e and + add.getType() instanceof TypeString and + formatStringValue(add.getLeftOperand(), left) and + formatStringValue(add.getRightOperand(), right) and + fmtvalue = left + right + ) or + formatStringValue(e.(ConditionalExpr).getTrueExpr(), fmtvalue) or + formatStringValue(e.(ConditionalExpr).getFalseExpr(), fmtvalue) or + exists(Method getprop, MethodAccess ma, string prop | + e = ma and + ma.getMethod() = getprop and + getprop.hasName("getProperty") and + getprop.getDeclaringType().hasQualifiedName("java.lang", "System") and + getprop.getNumberOfParameters() = 1 and + ma.getAnArgument().(StringLiteral).getRepresentedString() = prop and + (prop = "line.separator" or prop = "file.separator" or prop = "path.separator") and + fmtvalue = "x" // dummy value + ) or + exists(Field f | + e = f.getAnAccess() and + f.getDeclaringType().hasQualifiedName("java.io", "File") and + fmtvalue = "x" // dummy value + | + f.hasName("pathSeparator") or + f.hasName("pathSeparatorChar") or + f.hasName("separator") or + f.hasName("separatorChar") + ) + ) +} + +/** + * A string that is used as the format string in a `FormattingCall`. + */ +class FormatString extends string { + FormatString() { + formatStringValue(_, this) + } + + /** Gets a `FormattingCall` that uses this as its format string. */ + FormattingCall getAFormattingUse() { + exists(Expr fmt | formatStringValue(fmt, this) | + result.getFormatArgument() = fmt or + exists(VariableAssign va | + defUsePair(va, result.getFormatArgument()) and va.getSource() = fmt + ) or + result.getFormatArgument().(FieldAccess).getField().getAnAssignedValue() = fmt + ) + } + + /** + * Gets the largest argument index (1-indexed) that is referred by a format + * specifier. Gets the value 0 if there are no format specifiers. + */ + /*abstract*/ int getMaxFmtSpecIndex() { none() } + + /** + * Gets an argument index (1-indexed) less than `getMaxFmtSpecIndex()` that + * is not referred by any format specifier. + */ + /*abstract*/ int getASkippedFmtSpecIndex() { none() } +} + +private class PrintfFormatString extends FormatString { + PrintfFormatString() { + this.getAFormattingUse().getSyntax() = TFmtPrintf() + } + + /** + * Gets a boolean value that indicates whether the `%` character at index `i` + * is an escaped percentage sign or a format specifier. + */ + private boolean isEscapedPct(int i) { + this.charAt(i) = "%" and + if this.charAt(i-1) = "%" then + result = this.isEscapedPct(i-1).booleanNot() + else + result = false + } + + /** Holds if the format specifier at index `i` is a reference to an argument. */ + private predicate fmtSpecIsRef(int i) { + false = this.isEscapedPct(i) and + this.charAt(i) = "%" and + exists(string c | + c = this.charAt(i+1) and + c != "%" and + c != "n" + ) + } + + /** + * Holds if the format specifier at index `i` refers to the same argument as + * the preceding format specifier. + */ + private predicate fmtSpecRefersToPrevious(int i) { + this.fmtSpecIsRef(i) and + "<" = this.charAt(i+1) + } + + /** + * Gets the index of the specific argument (1-indexed) that the format + * specifier at index `i` refers to, if any. + */ + private int fmtSpecRefersToSpecificIndex(int i) { + this.fmtSpecIsRef(i) and + exists(string num | + result = num.toInt() + | + num = this.charAt(i+1) and "$" = this.charAt(i+2) or + num = this.charAt(i+1) + this.charAt(i+2) and "$" = this.charAt(i+3) + ) + } + + /** + * Holds if the format specifier at index `i` refers to the next argument in + * sequential order. + */ + private predicate fmtSpecRefersToSequentialIndex(int i) { + this.fmtSpecIsRef(i) and + not exists(this.fmtSpecRefersToSpecificIndex(i)) and + not this.fmtSpecRefersToPrevious(i) + } + + override int getMaxFmtSpecIndex() { + result = max(int ix | + ix = fmtSpecRefersToSpecificIndex(_) or + ix = count(int i | fmtSpecRefersToSequentialIndex(i)) + ) + } + + override int getASkippedFmtSpecIndex() { + result in [1..getMaxFmtSpecIndex()] and + result > count(int i | fmtSpecRefersToSequentialIndex(i)) and + not result = fmtSpecRefersToSpecificIndex(_) + } +} + +private class LoggerFormatString extends FormatString { + LoggerFormatString() { + this.getAFormattingUse().getSyntax() = TFmtLogger() + } + + /** + * Gets a boolean value that indicates whether the `\` character at index `i` + * is an unescaped backslash. + */ + private boolean isUnescapedBackslash(int i) { + this.charAt(i) = "\\" and + if this.charAt(i-1) = "\\" then + result = this.isUnescapedBackslash(i-1).booleanNot() + else + result = true + } + + /** Holds if an unescaped placeholder `{}` occurs at index `i`. */ + private predicate fmtPlaceholder(int i) { + this.charAt(i) = "{" and + this.charAt(i+1) = "}" and + not true = isUnescapedBackslash(i-1) + } + + override int getMaxFmtSpecIndex() { + result = count(int i | fmtPlaceholder(i)) + } +} diff --git a/java/ql/src/semmle/code/java/Type.qll b/java/ql/src/semmle/code/java/Type.qll new file mode 100755 index 00000000000..e8eccbb68a3 --- /dev/null +++ b/java/ql/src/semmle/code/java/Type.qll @@ -0,0 +1,1045 @@ +/** + * Provides classes and predicates for working with Java types. + * + * Types can be primitive types (`PrimitiveType`), array types (`Array`), or reference + * types (`RefType`), where the latter are either classes (`Class`) or interfaces + * (`Interface`). + * + * Reference types can be at the top level (`TopLevelType`) or nested (`NestedType`). + * Classes can also be local (`LocalClass`) or anonymous (`AnonymousClass`). + * Enumerated types (`EnumType`) are a special kind of class. + */ + +import Member +import Modifier +import JDK + +/** + * Holds if reference type `t` is an immediate super-type of `sub`. + */ +cached +predicate hasSubtype(RefType t, Type sub) { + // Direct subtype. + (extendsReftype(sub, t) and t != sub) or + implInterface(sub, t) or + // A parameterized type `T` is a subtype of the corresponding raw type `T<>`. + (parSubtypeRaw(t, sub) and t != sub) or + // Array subtyping is covariant. + (arraySubtype(t, sub) and t != sub) or + // Type parameter containment for parameterized types. + (parContainmentSubtype(t, sub) and t != sub) or + // Type variables are subtypes of their upper bounds. + (typeVarSubtypeBound(t, sub) and t != sub) +} + +private +predicate typeVarSubtypeBound(RefType t, TypeVariable tv) { + if tv.hasTypeBound() then + t = tv.getATypeBound().getType() + else + t instanceof TypeObject +} + +private +predicate parSubtypeRaw(RefType t, ParameterizedType sub) { + t = sub.getErasure().(GenericType).getRawType() +} + +private +predicate arraySubtype(Array sup, Array sub) { + hasSubtype(sup.getComponentType(), sub.getComponentType()) +} + +/* + * `parContainmentSubtype(pt, psub)` is equivalent to: + * ``` + * pt != psub and + * pt.getGenericType() = psub.getGenericType() and + * forex(int i | i in [0..pt.getNumberOfTypeArguments()-1] | + * typeArgumentContains(_, pt.getTypeArgument(i), psub.getTypeArgument(i), _) + * ) + * ``` + * For performance several transformations are made. First, the `forex` is + * written as a loop where `typeArgumentsContain(_, pt, psub, n)` encode that + * the `forex` holds for `i in [0..n]`. Second, the relation is split into two + * cases depending on whether `pt.getNumberOfTypeArguments()` is 1 or 2+, as + * this allows us to unroll the loop and collapse the first two iterations. The + * base case for `typeArgumentsContain` is therefore `n=1` and this allows an + * improved join order implemented by `contains01`. + */ +private +predicate parContainmentSubtype(ParameterizedType pt, ParameterizedType psub) { + pt != psub and + typeArgumentsContain(_, pt, psub, pt.getNumberOfTypeArguments()-1) + or + typeArgumentsContain0(_, pt, psub) +} + +/** + * Gets the `index`-th type parameter of `t`, which is a parameterization of `g`. + */ +private +RefType parameterisationTypeArgument(GenericType g, ParameterizedType t, int index) { + g = t.getGenericType() and + result = t.getTypeArgument(index) +} + +private predicate varianceCandidate(ParameterizedType pt) { + pt.getATypeArgument() instanceof Wildcard +} + +pragma[noinline] +private RefType parameterisationTypeArgumentVarianceCand(GenericType g, ParameterizedType t, int index) { + result = parameterisationTypeArgument(g, t, index) and + varianceCandidate(t) +} + +/** + * Holds if every type argument of `s` (up to `n` with `n >= 1`) contains the + * corresponding type argument of `t`. Both `s` and `t` are constrained to + * being parameterizations of `g`. + */ +pragma[nomagic] +private +predicate typeArgumentsContain(GenericType g, ParameterizedType s, ParameterizedType t, int n) { + contains01(g, s, t) and n = 1 + or + contains(g, s, t, n) and + typeArgumentsContain(g, s, t, n-1) +} + +private predicate typeArgumentsContain0(GenericType g, ParameterizedType sParm, ParameterizedType tParm) { + exists(RefType s, RefType t | + containsAux0(g, tParm, s, t) and + s = parameterisationTypeArgument(g, sParm, 0) and + s != t + ) +} + +/** + * Holds if the `n`-th type argument of `sParm` contain the `n`-th type + * argument of `tParm` for both `n = 0` and `n = 1`, where both `sParm` and + * `tParm` are parameterizations of the same generic type `g`. + * + * This is equivalent to + * ``` + * contains(g, sParm, tParm, 0) and + * contains(g, sParm, tParm, 1) + * ``` + * except `contains` is restricted to only include `n >= 2`. + */ +private predicate contains01(GenericType g, ParameterizedType sParm, ParameterizedType tParm) { + exists(RefType s0, RefType t0, RefType s1, RefType t1 | + contains01Aux0(g, tParm, s0, t0, t1) and + contains01Aux1(g, sParm, s0, s1, t1) + ) +} + +pragma[nomagic] +private predicate contains01Aux0(GenericType g, ParameterizedType tParm, RefType s0, RefType t0, RefType t1) { + typeArgumentContains(g, s0, t0, 0) and + t0 = parameterisationTypeArgument(g, tParm, 0) and + t1 = parameterisationTypeArgument(g, tParm, 1) +} + +pragma[nomagic] +private predicate contains01Aux1(GenericType g, ParameterizedType sParm, RefType s0, RefType s1, RefType t1) { + typeArgumentContains(g, s1, t1, 1) and + s0 = parameterisationTypeArgumentVarianceCand(g, sParm, 0) and + s1 = parameterisationTypeArgumentVarianceCand(g, sParm, 1) +} + +pragma[nomagic] +private predicate containsAux0(GenericType g, ParameterizedType tParm, RefType s, RefType t) { + typeArgumentContains(g, s, t, 0) and + t = parameterisationTypeArgument(g, tParm, 0) and + g.getNumberOfTypeParameters() = 1 +} + +/** + * Holds if the `n`-th type argument of `sParm` contain the `n`-th type + * argument of `tParm`, where both `sParm` and `tParm` are parameterizations of + * the same generic type `g`. The index `n` is restricted to `n >= 2`, the + * cases `n < 2` are handled by `contains01`. + * + * See JLS 4.5.1, Type Arguments of Parameterized Types. + */ +private +predicate contains(GenericType g, ParameterizedType sParm, ParameterizedType tParm, int n) { + exists(RefType s, RefType t | + containsAux(g, tParm, n, s, t) and + s = parameterisationTypeArgumentVarianceCand(g, sParm, n) + ) +} + +pragma[nomagic] +private predicate containsAux(GenericType g, ParameterizedType tParm, int n, RefType s, RefType t) { + typeArgumentContains(g, s, t, n) and + t = parameterisationTypeArgument(g, tParm, n) and + n >= 2 +} + +/** + * Holds if the type argument `s` contains the type argument `t`, where both + * type arguments occur as index `n` in an instantiation of `g`. + */ +pragma[noinline] +private predicate typeArgumentContains(GenericType g, RefType s, RefType t, int n) { + typeArgumentContainsAux2(g, s, t, n) and + s = parameterisationTypeArgumentVarianceCand(g, _, n) +} + +pragma[nomagic] +private predicate typeArgumentContainsAux2(GenericType g, RefType s, RefType t, int n) { + typeArgumentContainsAux1(s, t, n) and + t = parameterisationTypeArgument(g, _, n) +} + +/** + * Holds if the type argument `s` contains the type argument `t`, where both + * type arguments occur as index `n` in some parameterized types. + * + * See JLS 4.5.1, Type Arguments of Parameterized Types. + */ +private +predicate typeArgumentContainsAux1(RefType s, RefType t, int n) { + exists(int i | + s = parameterisationTypeArgumentVarianceCand(_, _, i) and + t = parameterisationTypeArgument(_, _, n) and + i <= n and n <= i + | + exists(RefType tUpperBound | tUpperBound = t.(Wildcard).getUpperBound().getType() | + // ? extends T <= ? extends S if T <: S + hasSubtypeStar0(s.(Wildcard).getUpperBound().getType(), tUpperBound) or + // ? extends T <= ? + s.(Wildcard).isUnconstrained() + ) or + exists(RefType tLowerBound | tLowerBound = t.(Wildcard).getLowerBound().getType() | + // ? super T <= ? super S if s <: T + hasSubtypeStar0(tLowerBound, s.(Wildcard).getLowerBound().getType()) or + // ? super T <= ? + s.(Wildcard).isUnconstrained() or + // ? super T <= ? extends Object + wildcardExtendsObject(s) + ) or + // T <= T + s = t or + // T <= ? extends T + hasSubtypeStar0(s.(Wildcard).getUpperBound().getType(), t) or + // T <= ? super T + hasSubtypeStar0(t, s.(Wildcard).getLowerBound().getType()) + ) +} + +pragma[noinline] +private predicate wildcardExtendsObject(Wildcard wc) { + wc.getUpperBound().getType() instanceof TypeObject +} + +/** + * DEPRECATED: Use `hasSubtype*` instead. + */ +deprecated predicate hasSubtypeStar(RefType t, RefType sub) { + hasSubtype*(t, sub) +} + +private predicate hasSubtypeStar0(RefType t, RefType sub) { + sub = t + or + hasSubtype(t, sub) + or + exists(RefType mid | hasSubtypeStar0(t, mid) and hasSubtype(mid, sub)) +} + +/** Holds if type `t` declares member `m`. */ +predicate declaresMember(Type t, @member m) { + methods(m,_,_,_,t,_) + or + constrs(m,_,_,_,t,_) + or + fields(m,_,_,t,_) + or + enclInReftype(m,t) and + // Since the type `@member` in the dbscheme includes all `@reftype`s, + // anonymous and local classes need to be excluded here. + not m instanceof AnonymousClass and + not m instanceof LocalClass +} + +/** + * A common abstraction for all Java types, including + * primitive, class, interface and array types. + */ +class Type extends Element, @type { + /** + * Gets the JVM descriptor for this type, as used in bytecode. + */ + string getTypeDescriptor() { none() } + + /** Gets the erasure of this type. */ + Type getErasure() { result = erase(this) } +} + +/** + * An array type. + * + * Array types are implicitly declared when used; there is + * an array declaration for each array type used in the system. + */ +class Array extends RefType, @array { + /** + * Gets the type of the components of this array type. + * + * For example, the component type of `Object[][]` is `Object[]`. + */ + Type getComponentType() { arrays(this, _, _, _, result) } + + /** + * Gets the type of the elements used to construct this array type. + * + * For example, the element type of `Object[][]` is `Object`. + */ + Type getElementType() { arrays(this, _, result, _, _) } + + /** + * Gets the arity of this array type. + * + * For example, the dimension of `Object[][]` is 2. + */ + int getDimension() { arrays(this, _, _, result, _) } + + /** + * Gets the JVM descriptor for this type, as used in bytecode. + */ + override string getTypeDescriptor() { + result = "[" + this.getComponentType().getTypeDescriptor() + } +} + +/** + * A common super-class for various kinds of reference types, + * including classes, interfaces, type parameters and arrays. + */ +class RefType extends Type, Annotatable, Modifiable, @reftype { + /** Gets the package in which this type is declared. */ + Package getPackage() { + classes(this,_,result,_) or + interfaces(this,_,result,_) + } + + /** Gets the type in which this reference type is enclosed, if any. */ + RefType getEnclosingType() { + enclInReftype(this, result) + } + + /** Gets the compilation unit in which this type is declared. */ + override CompilationUnit getCompilationUnit() { result = this.getFile() } + + /** Holds if `t` is an immediate supertype of this type. */ + predicate hasSupertype(RefType t) { hasSubtype(t,this) } + + /** Holds if `t` is an immediate subtype of this type. */ + predicate hasSubtype(RefType t) { hasSubtype(this,t) } + + /** Gets a direct subtype of this type. */ + RefType getASubtype() { hasSubtype(this,result) } + + /** Gets a direct supertype of this type. */ + RefType getASupertype() { hasSubtype(result,this) } + + /** Gets a direct or indirect supertype of this type, including itself. */ + RefType getAnAncestor() { hasSubtype*(result, this) } + + /** + * Gets the source declaration of a direct supertype of this type, excluding itself. + * + * Note, that a generic type is the source declaration of a direct supertype + * of itself, namely the corresponding raw type, and this case is thus + * explicitly excluded. See also `getSourceDeclaration()`. + */ + pragma[noinline] + RefType getASourceSupertype() { + result = this.getASupertype().getSourceDeclaration() and + result != this + } + + /** + * Holds if `t` is an immediate super-type of this type using only the immediate + * `extends` or `implements` relationships. In particular, this excludes + * parameter containment sub-typing for parameterized types. + */ + predicate extendsOrImplements(RefType t) { + extendsReftype(this, t) or + implInterface(this, t) or + typeVarSubtypeBound(t, this) + } + + /** Holds if this type declares any members. */ + predicate hasMember() { exists(getAMember()) } + + /** Gets a member declared in this type. */ + Member getAMember() { this = result.getDeclaringType() } + + /** Gets a method declared in this type. */ + Method getAMethod() { this = result.getDeclaringType() } + + /** Gets a constructor declared in this type. */ + Constructor getAConstructor() { this = result.getDeclaringType() } + + /** Gets a method or constructor declared in this type. */ + Callable getACallable() { this = result.getDeclaringType() } + + /** Gets a field declared in this type. */ + Field getAField() { this = result.getDeclaringType() } + + /** Holds if this type declares a method with the specified name. */ + predicate declaresMethod(string name) { this.getAMethod().getName() = name } + + /** Holds if this type declares a method with the specified name and number of parameters. */ + predicate declaresMethod(string name, int n) { + exists(Method m | m = this.getAMethod() | + m.getName() = name and + m.getNumberOfParameters() = n + ) + } + + /** Holds if this type declares a field with the specified name. */ + predicate declaresField(string name) { this.getAField().getName() = name } + + /** Gets the number of methods declared in this type. */ + int getNumberOfMethods() { result = count(Method m | m.getDeclaringType() = this) } + + /** + * Holds if this type declares or inherits method `m`, which is declared + * in `declaringType`. + */ + predicate hasMethod(Method m, RefType declaringType) { + hasMethod(m, declaringType, false) + } + + /** + * Holds if this type declares or inherits method `m`, which is declared + * in `declaringType`. Methods that would be inherited if they were public, + * but are not inherited due to being package protected, are also included + * and indicated by `hidden` being true. + */ + cached + predicate hasMethod(Method m, RefType declaringType, boolean hidden) { + hasNonInterfaceMethod(m, declaringType, hidden) or + hasInterfaceMethod(m, declaringType) and hidden = false + } + + private predicate noMethodExtraction() { + not methods(_,_,_,_,this,_) and + exists(Method m | methods(m,_,_,_,getSourceDeclaration(),_) and m.isInheritable()) + } + + private predicate canInheritFromSupertype(RefType sup) { + sup = getASupertype() and + (noMethodExtraction() implies supertypeSrcDecl(sup, getSourceDeclaration())) + } + + pragma[nomagic] + private predicate supertypeSrcDecl(RefType sup, RefType srcDecl) { + sup = getASupertype() and + srcDecl = sup.getSourceDeclaration() + } + + private predicate hasNonInterfaceMethod(Method m, RefType declaringType, boolean hidden) { + m = getAMethod() and this = declaringType and not declaringType instanceof Interface and hidden = false or + exists(RefType sup, boolean h1, boolean h2 | + (if m.isPackageProtected() and sup.getPackage() != this.getPackage() then h1 = true else h1 = false) and + (not sup instanceof Interface or this instanceof Interface) and + canInheritFromSupertype(sup) and + sup.hasNonInterfaceMethod(m, declaringType, h2) and + hidden = h1.booleanOr(h2) and + exists(string signature | methods(m,_,signature,_,_,_) and not methods(_,_,signature,_,this,_)) and + m.isInheritable() + ) + } + + private predicate cannotInheritInterfaceMethod(string signature) { + methods(_,_,signature,_,this,_) or + exists(Method m | hasNonInterfaceMethod(m, _, false) and methods(m,_,signature,_,_,_)) + } + + private predicate interfaceMethodCandidateWithSignature(Method m, string signature, RefType declaringType) { + m = getAMethod() and this = declaringType and declaringType instanceof Interface and methods(m,_,signature,_,_,_) or + exists(RefType sup | + sup.interfaceMethodCandidateWithSignature(m, signature, declaringType) and + not cannotInheritInterfaceMethod(signature) and + canInheritFromSupertype(sup) and + m.isInheritable() + ) + } + + pragma[nomagic] + private predicate overrideEquivalentInterfaceMethodCandidates(Method m1, Method m2) { + exists(string signature | + interfaceMethodCandidateWithSignature(m1, signature, _) and + interfaceMethodCandidateWithSignature(m2, signature, _) and + m1 != m2 and + m2.overrides(_) and + any(Method m).overrides(m1) + ) + } + + pragma[noinline] + private predicate overriddenInterfaceMethodCandidate(Method m) { + exists(Method m2 | + overrideEquivalentInterfaceMethodCandidates(m, m2) and + m2.overrides(m) + ) + } + + private predicate hasInterfaceMethod(Method m, RefType declaringType) { + interfaceMethodCandidateWithSignature(m, _, declaringType) and + not overriddenInterfaceMethodCandidate(m) + } + + /** Holds if this type declares or inherits the specified member. */ + predicate inherits(Member m) { + exists(Field f | f = m | + f = getAField() or + not f.isPrivate() and not declaresField(f.getName()) and getASupertype().inherits(f) or + getSourceDeclaration().inherits(f) + ) + or + hasMethod((Method)m, _) + } + + /** Holds if this is a top-level type, which is not nested inside any other types. */ + predicate isTopLevel() { this instanceof TopLevelType } + + /** Holds if this type is declared in a specified package with the specified name. */ + predicate hasQualifiedName(string package, string type) { + this.getPackage().hasName(package) and type = this.nestedName() + } + + /** + * Gets the JVM descriptor for this type, as used in bytecode. + */ + override string getTypeDescriptor() { + result = "L" + this.getPackage().getName().replaceAll(".", "/") + "/" + + this.getSourceDeclaration().nestedName() + ";" + } + + /** + * Gets the qualified name of this type. + */ + string getQualifiedName() { + exists(string pkgName | pkgName = getPackage().getName() | + if pkgName = "" then + result = nestedName() + else + result = pkgName + "." + nestedName() + ) + } + + /** Gets the nested name of this type. */ + string nestedName() { + not this instanceof NestedType and result = this.getName() + or + this.(NestedType).getEnclosingType().nestedName() + "$" + this.getName() = result + } + + /** + * Gets the source declaration of this type. + * + * For parameterized instances of generic types and raw types, the + * source declaration is the corresponding generic type. + * + * For non-parameterized types declared inside a parameterized + * instance of a generic type, the source declaration is the + * corresponding type in the generic type. + * + * For all other types, the source declaration is the type itself. + */ + RefType getSourceDeclaration() { result = this } + + /** Holds if this type is the same as its source declaration. */ + predicate isSourceDeclaration() { this.getSourceDeclaration() = this } + + /** Cast this reference type to a class that provides access to metrics information. */ + MetricRefType getMetrics() { result = this } + + /** + * A common (reflexive, transitive) subtype of the erasures of + * types `t1` and `t2`, if it exists. + * + * If there is no such common subtype, then the two types are disjoint. + * However, the converse is not true; for example, the parameterized types + * `List` and `Collection` are disjoint, + * but their erasures (`List` and `Collection`, respectively) + * do have common subtypes (such as `List` itself). + * + * For the definition of the notion of *erasure* see JLS v8, section 4.6 (Type Erasure). + */ + pragma[inline] + RefType commonSubtype(RefType other) { + result.getASourceSupertype*() = erase(this) and + result.getASourceSupertype*() = erase(other) + } +} + +/** A type that is the same as its source declaration. */ +class SrcRefType extends RefType { + SrcRefType() { this.isSourceDeclaration() } +} + +/** A class declaration. */ +class Class extends RefType, @class { + /** Holds if this class is an anonymous class. */ + predicate isAnonymous() { isAnonymClass(this,_) } + + /** Holds if this class is a local class. */ + predicate isLocal() { isLocalClass(this,_) } + + override RefType getSourceDeclaration() { classes(this,_,_,result) } + + /** + * Gets an annotation that applies to this class. + * + * Note that a class may inherit annotations from super-classes. + */ + override Annotation getAnAnnotation() { + result = RefType.super.getAnAnnotation() or + exists(AnnotationType tp | tp = result.getType() | + tp.isInherited() and + not exists(Annotation ann | ann = RefType.super.getAnAnnotation() | ann.getType() = tp) and + result = this.getASupertype().(Class).getAnAnnotation() + ) + } +} + +/** An intersection type. */ +class IntersectionType extends RefType, @class { + IntersectionType() { + exists(string shortname | + classes(this, shortname, _, _) and + shortname.matches("% & ...") + ) + } + private RefType superType() { extendsReftype(this, result) } + private RefType superInterface() { implInterface(this, result) } + string getLongName() { + result = superType().toString() + concat(" & " + superInterface().toString()) + } + RefType getFirstBound() { + extendsReftype(this, result) + } +} + +/** An anonymous class. */ +class AnonymousClass extends NestedClass { + AnonymousClass() { this.isAnonymous() } + + /** + * Utility method: an integer that is larger for classes that + * are defined textually later. + */ + private int rankInParent(RefType parent) { + this.getEnclosingType() = parent and + exists(Location myLocation, File f, int maxCol | myLocation = this.getLocation() | + f = myLocation.getFile() and + maxCol = max(Location loc | loc.getFile() = f | loc.getStartColumn()) and + result = myLocation.getStartLine() * maxCol + myLocation.getStartColumn() + ) + } + + /** + * Gets the JVM descriptor for this type, as used in bytecode. + * + * For an anonymous class, the type descriptor is the descriptor of the + * enclosing type followed by a (1-based) counter of anonymous classes + * declared within that type. + */ + override string getTypeDescriptor() { + exists(RefType parent | parent = this.getEnclosingType() | + exists(int num | num = 1 + count(AnonymousClass other | other.rankInParent(parent) < rankInParent(parent)) | + exists(string parentWithSemi | parentWithSemi = parent.getTypeDescriptor() | + result = parentWithSemi.prefix(parentWithSemi.length() - 1) + "$" + num + ";" + ) + ) + ) + } + + /** Gets the class instance expression where this anonymous class occurs. */ + ClassInstanceExpr getClassInstanceExpr() { isAnonymClass(this, result) } + + override string toString() { result = "new " + this.getClassInstanceExpr().getTypeName() + "(...) { ... }" } + + /** + * Gets the qualified name of this type. + * + * Anonymous classes do not have qualified names, so we use + * the string `""` as a placeholder. + */ + override string getQualifiedName() { result = "" } +} + +/** A local class. */ +class LocalClass extends NestedClass { + LocalClass() { this.isLocal() } + + /** Gets the statement that declares this local class. */ + LocalClassDeclStmt getLocalClassDeclStmt() { isLocalClass(this, result) } +} + +/** A top-level type. */ +class TopLevelType extends RefType { + TopLevelType() { + not enclInReftype(this,_) and + (this instanceof Class or this instanceof Interface) + } +} + +/** A top-level class. */ +class TopLevelClass extends TopLevelType, Class { +} + +/** A nested type is a type declared within another type. */ +class NestedType extends RefType { + NestedType() { + enclInReftype(this,_) + } + + /** Gets the type enclosing this nested type. */ + override RefType getEnclosingType() { + enclInReftype(this,result) + } + + /** Gets the nesting depth of this nested type. Top-level types have nesting depth 0. */ + int getNestingDepth() { + if getEnclosingType() instanceof NestedType then + result = getEnclosingType().(NestedType).getNestingDepth() + 1 + else + result = 1 + } + + override predicate isPublic() { + super.isPublic() or + // JLS 9.5: A member type declaration in an interface is implicitly public and static + exists(Interface i | this = i.getAMember()) + } + + override predicate isStrictfp() { + super.isStrictfp() or + // JLS 8.1.1.3, JLS 9.1.1.2 + getEnclosingType().isStrictfp() + } + + /** + * Holds if this nested type is static. + * + * A nested type is static either if it is explicitly declared as such + * using the modifier `static`, or if it is implicitly static + * because one of the following holds: + * + * - it is a member type of an interface, + * - it is a member interface, or + * - it is a nested enum type. + * + * See JLS v8, section 8.5.1 (Static Member Type Declarations), + * section 8.9 (Enums) and section 9.5 (Member Type Declarations). + */ + override predicate isStatic() { + super.isStatic() or + // JLS 8.5.1: A member interface is implicitly static. + this instanceof Interface or + // JLS 8.9: A nested enum type is implicitly static. + this instanceof EnumType or + // JLS 9.5: A member type declaration in an interface is implicitly public and static + exists(Interface i | this = i.getAMember()) + } +} + +/** + * A class declared within another type. + * + * This includes (static and non-static) member classes, + * local classes and anonymous classes. + */ +class NestedClass extends NestedType, Class { +} + +/** + * An inner class is a nested class that is neither + * explicitly nor implicitly declared static. + */ +class InnerClass extends NestedClass { + InnerClass() { + not this.isStatic() + } + + /** + * Holds if an instance of this inner class holds a reference to its + * enclosing class. + */ + predicate hasEnclosingInstance() { + // JLS 15.9.2. Determining Enclosing Instances + not this.(AnonymousClass).getClassInstanceExpr().isInStaticContext() and + not this.(LocalClass).getLocalClassDeclStmt().getEnclosingCallable().isStatic() + } +} + +/** An interface. */ +class Interface extends RefType, @interface { + override RefType getSourceDeclaration() { interfaces(this,_,_,result) } + + override predicate isAbstract() { + // JLS 9.1.1.1: "Every interface is implicitly abstract" + any() + } +} + +/** A class or interface. */ +class ClassOrInterface extends RefType { + ClassOrInterface() { + this instanceof Class or + this instanceof Interface + } +} + +/** + * A primitive type. + * + * This includes `boolean`, `byte`, `short`, + * `char`, `int`, `long`, `float`, + * and `double`. + */ +class PrimitiveType extends Type, @primitive { + PrimitiveType() { + this.getName().regexpMatch("float|double|int|boolean|short|byte|char|long") + } + + /** Gets the boxed type corresponding to this primitive type. */ + BoxedType getBoxedType() { + result.getPrimitiveType() = this + } + + /** + * Gets the JVM descriptor for this type, as used in bytecode. + */ + override string getTypeDescriptor() { + (this.hasName("float") and result = "F") or + (this.hasName("double") and result = "D") or + (this.hasName("int") and result = "I") or + (this.hasName("boolean") and result = "Z") or + (this.hasName("short") and result = "S") or + (this.hasName("byte") and result = "B") or + (this.hasName("char") and result = "C") or + (this.hasName("long") and result = "J") + } + + /** + * Gets a default value for this primitive type, as assigned by the compiler + * for variables that are declared but not initialized explicitly. + * Typically zero for numeric and character types and `false` for `boolean`. + * + * For numeric primitive types, default literals of one numeric type are also + * considered to be default values of all other numeric types, even if they + * require an explicit cast. + */ + Literal getADefaultValue() { + getName() = "boolean" and result.getLiteral() = "false" or + getName() = "char" and (result.getLiteral() = "'\\0'" or result.getLiteral() = "'\\u0000'") or + getName().regexpMatch("(float|double|int|short|byte|long)") and result.getLiteral().regexpMatch("0(\\.0)?+[lLfFdD]?+") + } +} + +/** The type of the `null` literal. */ +class NullType extends Type, @primitive { + NullType() { this.hasName("") } +} + +/** The `void` type. */ +class VoidType extends Type, @primitive { + VoidType() { this.hasName("void") } + + /** + * Gets the JVM descriptor for this type, as used in bytecode. + */ + override string getTypeDescriptor() { + result = "V" + } +} + +/** + * A boxed type. + * + * This includes `Boolean`, `Byte`, `Short`, + * `Character`, `Integer`, `Long`, `Float`, + * and `Double`. + */ +class BoxedType extends RefType { + BoxedType() { + this.hasQualifiedName("java.lang", "Float") or + this.hasQualifiedName("java.lang", "Double") or + this.hasQualifiedName("java.lang", "Integer") or + this.hasQualifiedName("java.lang", "Boolean") or + this.hasQualifiedName("java.lang", "Short") or + this.hasQualifiedName("java.lang", "Byte") or + this.hasQualifiedName("java.lang", "Character") or + this.hasQualifiedName("java.lang", "Long") + } + + /** Gets the primitive type corresponding to this boxed type. */ + PrimitiveType getPrimitiveType() { + (this.hasName("Float") and result.hasName("float")) or + (this.hasName("Double") and result.hasName("double")) or + (this.hasName("Integer") and result.hasName("int")) or + (this.hasName("Boolean") and result.hasName("boolean")) or + (this.hasName("Short") and result.hasName("short")) or + (this.hasName("Byte") and result.hasName("byte")) or + (this.hasName("Character") and result.hasName("char")) or + (this.hasName("Long") and result.hasName("long")) + } +} + +/** + * An enumerated type. + * + * Each enum type has zero or more enum constants which can + * be enumerated over. + * The type of an enum constant is the enum type itself. + * + * For example, + * + * ``` + * enum X { A, B, C } + * ``` + * is an enum type declaration, where the type of the enum + * constant `X.A` is `X`. + */ +class EnumType extends Class { + EnumType() { isEnumType(this) } + + /** Gets the enum constant with the specified name. */ + EnumConstant getEnumConstant(string name) { + fields(result,_,_,this,_) and result.hasName(name) + } + + /** Gets an enum constant declared in this enum type. */ + EnumConstant getAnEnumConstant() { + fields(result,_,_,this,_) + } + + override predicate isFinal() { + // JLS 8.9: An enum declaration is implicitly `final` unless it contains + // at least one enum constant that has a class body. + not getAnEnumConstant().getAnAssignedValue().getType() instanceof AnonymousClass + } +} + +/** An enum constant is a member of a enum type. */ +class EnumConstant extends Field { + EnumConstant() { isEnumConst(this) } + + // JLS 8.9.3: For each enum constant `c` in the body of the declaration of + // [enum type] `E`, `E` has an implicitly declared `public static final` + // field of type `E` that has the same name as `c`. + override predicate isPublic() { any() } + override predicate isStatic() { any() } + override predicate isFinal() { any() } +} + +/** + * Gets the erasure of a type. + * + * See JLS v8, section 4.6 (Type Erasure). + */ +private cached Type erase(Type t) { + result = t.(Class).getSourceDeclaration() and not t instanceof IntersectionType or + result = erase(t.(IntersectionType).getFirstBound()) or + result = t.(Interface).getSourceDeclaration() or + result.(Array).getComponentType() = erase(t.(Array).getComponentType()) or + result = erase(t.(BoundedType).getFirstUpperBoundType()) or + result = (NullType)t or + result = (VoidType)t or + result = (PrimitiveType)t +} + +/** + * Is there a common (reflexive, transitive) subtype of the erasures of + * types `t1` and `t2`? + * + * If there is no such common subtype, then the two types are disjoint. + * However, the converse is not true; for example, the parameterized types + * `List` and `Collection` are disjoint, + * but their erasures (`List` and `Collection`, respectively) + * do have common subtypes (such as `List` itself). + * + * For the definition of the notion of *erasure* see JLS v8, section 4.6 (Type Erasure). + */ +pragma[inline] +predicate haveIntersection(RefType t1, RefType t2) { + exists(RefType e1, RefType e2 | e1 = erase(t1) and e2 = erase(t2) | + erasedHaveIntersection(e1, e2) + ) +} + +/** + * Holds if there is a common (reflexive, transitive) subtype of the erased + * types `t1` and `t2`. + */ +predicate erasedHaveIntersection(RefType t1, RefType t2) { + exists(SrcRefType commonSub | commonSub.getASourceSupertype*() = t1 and commonSub.getASourceSupertype*() = t2) and + t1 = erase(_) and + t2 = erase(_) +} + +/** An integral type, which may be either a primitive or a boxed type. */ +class IntegralType extends Type { + IntegralType() { + exists(string name | + name = this.(PrimitiveType).getName() or name = this.(BoxedType).getPrimitiveType().getName() + | + name.regexpMatch("byte|char|short|int|long") + ) + } +} + +/** A boolean type, which may be either a primitive or a boxed type. */ +class BooleanType extends Type { + BooleanType() { + exists(string name | + name = this.(PrimitiveType).getName() or name = this.(BoxedType).getPrimitiveType().getName() + | + name = "boolean" + ) + } +} + +/** A character type, which may be either a primitive or a boxed type. */ +class CharacterType extends Type { + CharacterType() { + exists(string name | + name = this.(PrimitiveType).getName() or name = this.(BoxedType).getPrimitiveType().getName() + | + name = "char" + ) + } +} + +/** A numeric or character type, which may be either a primitive or a boxed type. */ +class NumericOrCharType extends Type { + NumericOrCharType() { + exists(string name | + name = this.(PrimitiveType).getName() or name = this.(BoxedType).getPrimitiveType().getName() + | + name.regexpMatch("byte|char|short|int|long|double|float") + ) + } +} + +/** A floating point type, which may be either a primitive or a boxed type. */ +class FloatingPointType extends Type { + FloatingPointType() { + exists(string name | + name = this.(PrimitiveType).getName() or name = this.(BoxedType).getPrimitiveType().getName() + | + name.regexpMatch("float|double") + ) + } +} diff --git a/java/ql/src/semmle/code/java/UnitTests.qll b/java/ql/src/semmle/code/java/UnitTests.qll new file mode 100644 index 00000000000..b058c1f1789 --- /dev/null +++ b/java/ql/src/semmle/code/java/UnitTests.qll @@ -0,0 +1,325 @@ +/** + * Provides classes and predicates for working with test classes and methods. + */ + +import Type +import Member +import semmle.code.java.frameworks.JUnitAnnotations + +/** The Java class `junit.framework.TestCase`. */ +class TypeJUnitTestCase extends RefType { + TypeJUnitTestCase() { + this.hasQualifiedName("junit.framework", "TestCase") + } +} + +/** The Java interface `junit.framework.Test`. */ +class TypeJUnitTest extends RefType { + TypeJUnitTest() { + this.hasQualifiedName("junit.framework", "Test") + } +} + +/** The Java class `junit.framework.TestSuite`. */ +class TypeJUnitTestSuite extends RefType { + TypeJUnitTestSuite() { + this.hasQualifiedName("junit.framework", "TestSuite") + } +} + +/** A JUnit 3.8 test class. */ +class JUnit38TestClass extends Class { + JUnit38TestClass() { + exists(TypeJUnitTestCase tc | this.hasSupertype+(tc)) + } +} + +/** A JUnit 3.8 `tearDown` method. */ +class TearDownMethod extends Method { + TearDownMethod() { + this.hasName("tearDown") and + this.hasNoParameters() and + this.getReturnType().hasName("void") and + exists(Method m | m.getDeclaringType() instanceof TypeJUnitTestCase | + this.overrides*(m) + ) + } +} + +/** + * A class detected to be a test class, either because it is a JUnit test class + * or because its name or the name of one of its super-types contains the substring "Test". + */ +class TestClass extends Class { + TestClass() { + this instanceof JUnit38TestClass or + this.getASupertype*().getSourceDeclaration().getName().matches("%Test%") + } +} + +/** + * A test method declared within a JUnit 3.8 test class. + */ +class JUnit3TestMethod extends Method { + JUnit3TestMethod() { + this.isPublic() and + this.getDeclaringType() instanceof JUnit38TestClass and + this.getName().matches("test%") and + this.getReturnType().hasName("void") and + this.hasNoParameters() + } +} + +/** + * A JUnit 3.8 test suite method. + */ +class JUnit3TestSuite extends Method { + JUnit3TestSuite() { + this.isPublic() and + this.isStatic() and + ( + this.getDeclaringType() instanceof JUnit38TestClass or + this.getDeclaringType().getAnAncestor() instanceof TypeJUnitTestSuite + ) and + this.hasName("suite") and + this.getReturnType() instanceof TypeJUnitTest and + this.hasNoParameters() + } +} + + +/** + * A JUnit test method that is annotated with the `org.junit.Test` annotation. + */ +class JUnit4TestMethod extends Method { + JUnit4TestMethod() { + this.getAnAnnotation().getType().hasQualifiedName("org.junit", "Test") + } +} + +/** + * A JUnit test method that is annotated with the `org.junit.jupiter.api.Test` annotation. + */ +class JUnitJupiterTestMethod extends Method { + JUnitJupiterTestMethod() { + this.getAnAnnotation().getType().hasQualifiedName("org.junit.jupiter.api", "Test") + } +} + +/** + * A JUnit `@Ignore` annotation. + */ +class JUnitIgnoreAnnotation extends Annotation { + JUnitIgnoreAnnotation() { + getType().hasQualifiedName("org.junit", "Ignore") + } +} + +/** + * A method which, directly or indirectly, is treated as ignored by JUnit due to a `@Ignore` + * annotation. + */ +class JUnitIgnoredMethod extends Method { + JUnitIgnoredMethod() { + getAnAnnotation() instanceof JUnitIgnoreAnnotation or + exists(Class c | + c = this.getDeclaringType() + | + c.getAnAnnotation() instanceof JUnitIgnoreAnnotation + ) + } +} + +/** + * An annotation in TestNG. + */ +class TestNGAnnotation extends Annotation { + TestNGAnnotation() { + getType().getPackage().hasName("org.testng.annotations") + } +} + +/** + * An annotation of type `org.test.ng.annotations.Test`. + */ +class TestNGTestAnnotation extends TestNGAnnotation { + TestNGTestAnnotation() { + getType().hasName("Test") + } +} + +/** + * A TestNG test method, annotated with the `org.testng.annotations.Test` annotation. + */ +class TestNGTestMethod extends Method { + TestNGTestMethod() { + this.getAnAnnotation() instanceof TestNGTestAnnotation + } + + /** + * Identify a possible `DataProvider` for this method, if the annotation includes a `dataProvider` + * value. + */ + TestNGDataProviderMethod getADataProvider() { + exists(TestNGTestAnnotation testAnnotation | + testAnnotation = getAnAnnotation() and + // The data provider must have the same name as the referenced data provider + result.getDataProviderName() = testAnnotation.getValue("dataProvider").(StringLiteral).getRepresentedString() + | + // Either the data provider should be on the current class, or a supertype + getDeclaringType().getAnAncestor() = result.getDeclaringType() or + // Or the data provider class should be declared + result.getDeclaringType() = testAnnotation.getValue("dataProviderClass").(TypeLiteral).getTypeName().getType() + ) + } +} + +/** + * Any method detected to be a test method of a common testing framework, + * including JUnit and TestNG. + */ +class TestMethod extends Method { + TestMethod() { + this instanceof JUnit3TestMethod or + this instanceof JUnit4TestMethod or + this instanceof JUnitJupiterTestMethod or + this instanceof TestNGTestMethod + } +} + +/** + * A TestNG annotation used to mark a method that runs "before". + */ +class TestNGBeforeAnnotation extends TestNGAnnotation { + TestNGBeforeAnnotation() { + getType().getName().matches("Before%") + } +} + +/** + * A TestNG annotation used to mark a method that runs "after". + */ +class TestNGAfterAnnotation extends TestNGAnnotation { + TestNGAfterAnnotation() { + getType().getName().matches("After%") + } +} + +/** + * An annotation of type `org.testng.annotations.DataProvider` which is applied to methods to mark + * them as data provider methods for TestNG. + */ +class TestNGDataProviderAnnotation extends TestNGAnnotation { + TestNGDataProviderAnnotation() { + getType().hasName("DataProvider") + } +} + +/** + * An annotation of type `org.testng.annotations.Factory` which is applied to methods to mark + * them as factory methods for TestNG. + */ +class TestNGFactoryAnnotation extends TestNGAnnotation { + TestNGFactoryAnnotation() { + getType().hasName("Factory") + } +} + +/** + * An annotation of type `org.testng.annotations.Listeners` which is applied to classes to define + * which listeners apply to them. + */ +class TestNGListenersAnnotation extends TestNGAnnotation { + TestNGListenersAnnotation() { + getType().hasName("Listeners") + } + + /** + * Gets a listener defined in this annotation. + */ + TestNGListenerImpl getAListener() { + result = getAValue("value").(TypeLiteral).getTypeName().getType() + } +} + +/** + * A concrete implementation class of one or more of the TestNG listener interfaces. + */ +class TestNGListenerImpl extends Class { + TestNGListenerImpl() { + getAnAncestor().hasQualifiedName("org.testng", "ITestNGListener") + } +} + +/** + * A method annotated with `org.testng.annotations.DataProvider` marking it as a data provider method + * for TestNG. + * + * This data provider method can be referenced by "name", and used by the test framework to provide + * an instance of a particular value when running a test method. + */ +class TestNGDataProviderMethod extends Method { + TestNGDataProviderMethod() { + getAnAnnotation() instanceof TestNGDataProviderAnnotation + } + + /** + * Gets the name associated with this data provider. + */ + string getDataProviderName() { + result = getAnAnnotation().(TestNGDataProviderAnnotation).getValue("name").(StringLiteral).getRepresentedString() + } +} + +/** + * A constructor or method annotated with `org.testng.annotations.Factory` marking it as a factory + * for TestNG. + * + * This factory callable is used to generate instances of parameterized test classes. + */ +class TestNGFactoryCallable extends Callable { + TestNGFactoryCallable() { + getAnAnnotation() instanceof TestNGFactoryAnnotation + } +} + +/** + * A class that will be run using the `org.junit.runners.Parameterized` JUnit runner. + */ +class ParameterizedJUnitTest extends Class { + ParameterizedJUnitTest() { + getAnAnnotation().(RunWithAnnotation).getRunner().(Class).hasQualifiedName("org.junit.runners", "Parameterized") + } +} + +/** + * A `@Category` annotation on a class or method, that categorizes the annotated test. + */ +class JUnitCategoryAnnotation extends Annotation { + JUnitCategoryAnnotation() { + getType().hasQualifiedName("org.junit.experimental.categories", "Category") + } + + /** + * One of the categories that this test is categorized as. + */ + Type getACategory() { + exists(TypeLiteral literal, Expr value | + value = getValue("value") and + ( + literal = value or + literal = value.(ArrayCreationExpr).getInit().getAnInit() + ) | + result = literal.getTypeName().getType() + ) + } +} + +/** + * A test class that will be run with theories. + */ +class JUnitTheoryTest extends Class { + JUnitTheoryTest() { + getAnAnnotation().(RunWithAnnotation).getRunner().(Class).hasQualifiedName("org.junit.experimental.theories", "Theories") + } +} diff --git a/java/ql/src/semmle/code/java/Variable.qll b/java/ql/src/semmle/code/java/Variable.qll new file mode 100755 index 00000000000..0147c19121b --- /dev/null +++ b/java/ql/src/semmle/code/java/Variable.qll @@ -0,0 +1,106 @@ +/** + * Provides classes and predicates for working with Java variables and their declarations. + */ + +import Element + +/** A variable is a field, a local variable or a parameter. */ +class Variable extends @variable, Annotatable, Element, Modifiable { + /** Gets the type of this variable. */ + /*abstract*/ Type getType() { none() } + + /** Gets an access to this variable. */ + VarAccess getAnAccess() { variableBinding(result,this) } + + /** Gets an expression on the right-hand side of an assignment to this variable. */ + Expr getAnAssignedValue() { + exists(LocalVariableDeclExpr e | e.getVariable() = this and result = e.getInit()) + or + exists(AssignExpr e | e.getDest().getProperExpr() = this.getAnAccess() and result = e.getSource()) + } + + /** Gets the initializer expression of this variable. */ + Expr getInitializer() { + none() + } + + /** Gets a printable representation of this variable together with its type. */ + string pp() { + result = this.getType().getName() + " " + this.getName() + } +} + +/** A locally scoped variable, that is, either a local variable or a parameter. */ +class LocalScopeVariable extends Variable, @localscopevariable { + /** Gets the callable in which this variable is declared. */ + abstract Callable getCallable(); +} + +/** A local variable declaration */ +class LocalVariableDecl extends @localvar, LocalScopeVariable { + /** Gets the type of this local variable. */ + override Type getType() { localvars(this,_,result,_) } + + /** Gets the expression declaring this variable. */ + LocalVariableDeclExpr getDeclExpr() { localvars(this, _, _, result) } + + /** Gets the parent of this declaration. */ + Expr getParent() { localvars(this,_,_,result) } + + /** Gets the callable in which this declaration occurs. */ + override Callable getCallable() { result = this.getParent().getEnclosingCallable() } + + /** Gets the callable in which this declaration occurs. */ + Callable getEnclosingCallable() { result = getCallable() } + + override string toString() { result = this.getType().getName() + " " + this.getName() } + + /** Gets the initializer expression of this local variable declaration. */ + override Expr getInitializer() { + result = getDeclExpr().getInit() + } +} + +/** A formal parameter of a callable. */ +class Parameter extends Element, @param, LocalScopeVariable { + /** Gets the type of this formal parameter. */ + override Type getType() { params(this,result,_,_,_) } + + /** Holds if the parameter is never assigned a value in the body of the callable. */ + predicate isEffectivelyFinal() { not exists(getAnAssignedValue()) } + + /** Gets the (zero-based) index of this formal parameter. */ + int getPosition() { params(this,_,result,_,_) } + + /** Gets the callable that declares this formal parameter. */ + override Callable getCallable() { params(this,_,_,result,_) } + + /** Gets the source declaration of this formal parameter. */ + Parameter getSourceDeclaration() { params(this,_,_,_,result) } + + /** Holds if this formal parameter is the same as its source declaration. */ + predicate isSourceDeclaration() { this.getSourceDeclaration() = this } + + /** Holds if this formal parameter is a variable arity parameter. */ + predicate isVarargs() { + isVarargsParam(this) + } + + /** + * Gets an argument for this parameter in any call to the callable that declares this formal + * parameter. + * + * Varargs parameters will have no results for this method. + */ + Expr getAnArgument() { + not isVarargs() and + result = getACallArgument(getPosition()) + } + + private pragma[noinline] Expr getACallArgument(int i) { + exists(Call call | + result = call.getArgument(i) and + call.getCallee().getSourceDeclaration().getAParameter() = this + ) + } +} diff --git a/java/ql/src/semmle/code/java/arithmetic/Overflow.qll b/java/ql/src/semmle/code/java/arithmetic/Overflow.qll new file mode 100644 index 00000000000..9a8c7e77f82 --- /dev/null +++ b/java/ql/src/semmle/code/java/arithmetic/Overflow.qll @@ -0,0 +1,124 @@ +import java + +/** A subclass of `PrimitiveType` with width-based ordering methods. */ +class OrdPrimitiveType extends PrimitiveType { + + predicate widerThan(OrdPrimitiveType that) { + getWidthRank() > that.getWidthRank() + } + + predicate widerThanOrEqualTo(OrdPrimitiveType that) { + getWidthRank() >= that.getWidthRank() + } + + OrdPrimitiveType maxType(OrdPrimitiveType that) { + (this.widerThan(that) and result = this) + or + (not this.widerThan(that) and result = that) + } + + int getWidthRank() { + (this.getName() = "byte" and result = 1) + or + (this.getName() = "short" and result = 2) + or + (this.getName() = "int" and result = 3) + or + (this.getName() = "long" and result = 4) + or + (this.getName() = "float" and result = 5) + or + (this.getName() = "double" and result = 6) + } + + float getMaxValue() { + (this.getName() = "byte" and result = 127.0) + or + (this.getName() = "short" and result = 32767.0) + or + (this.getName() = "int" and result = 2147483647.0) + or + (this.getName() = "long" and result = 9223372036854775807.0) + // don't try for floats and doubles + } + + float getMinValue() { + (this.getName() = "byte" and result = -128.0) + or + (this.getName() = "short" and result = -32768.0) + or + (this.getName() = "int" and result = -2147483648.0) + or + (this.getName() = "long" and result = -9223372036854775808.0) + // don't try for floats and doubles + } +} + +class NumType extends Type { + NumType() { + this instanceof PrimitiveType or + this instanceof BoxedType + } + + /** Gets the width-ordered primitive type corresponding to this type. */ + OrdPrimitiveType getOrdPrimitiveType() { + (this instanceof PrimitiveType and result = this) + or + (this instanceof BoxedType and result = this.(BoxedType).getPrimitiveType()) + } + + predicate widerThan(NumType that) { + this.getOrdPrimitiveType().widerThan(that.getOrdPrimitiveType()) + } + + predicate widerThanOrEqualTo(NumType that) { + this.getOrdPrimitiveType().widerThanOrEqualTo(that.getOrdPrimitiveType()) + } + + int getWidthRank() { + result = this.getOrdPrimitiveType().getWidthRank() + } +} + +class ArithExpr extends Expr { + ArithExpr() { + ( + this instanceof UnaryAssignExpr or + this instanceof AddExpr or this instanceof MulExpr or + this instanceof SubExpr or this instanceof DivExpr + ) and + forall(Expr e | e = this.(BinaryExpr).getAnOperand() or e = this.(UnaryAssignExpr).getExpr() | + e.getType() instanceof NumType + ) + } + + OrdPrimitiveType getOrdPrimitiveType() { + exists(OrdPrimitiveType t1, OrdPrimitiveType t2 | + t1 = this.getLeftOperand().getType().(NumType).getOrdPrimitiveType() and + t2 = this.getRightOperand().getType().(NumType).getOrdPrimitiveType() and + result = t1.maxType(t2) + ) + } + + /** + * Gets the left-hand operand of a binary expression + * or the operand of a unary assignment expression. + */ + Expr getLeftOperand() { + result = this.(BinaryExpr).getLeftOperand() or + result = this.(UnaryAssignExpr).getExpr() + } + + /** + * Gets the right-hand operand if this is a binary expression. + */ + Expr getRightOperand() { + result = this.(BinaryExpr).getRightOperand() + } + + /** Gets an operand of this arithmetic expression. */ + Expr getAnOperand() { + result = this.(BinaryExpr).getAnOperand() or + result = this.(UnaryAssignExpr).getExpr() + } +} diff --git a/java/ql/src/semmle/code/java/comparison/Comparison.qll b/java/ql/src/semmle/code/java/comparison/Comparison.qll new file mode 100644 index 00000000000..c6546fd715c --- /dev/null +++ b/java/ql/src/semmle/code/java/comparison/Comparison.qll @@ -0,0 +1,23 @@ +import java + +/** + * If `e1` evaluates to `b1` then the direct subexpression `e2` evaluates to `b2`. + * + * Used as basis for the transitive closure in `exprImplies`. + */ +private predicate exprImpliesStep(Expr e1, boolean b1, Expr e2, boolean b2) { + e1.(ParExpr).getProperExpr() = e2 and b2 = b1 and (b1 = true or b1 = false) + or + e1.(LogNotExpr).getExpr() = e2 and b2 = b1.booleanNot() and (b1 = true or b1 = false) + or + b1 = true and e1.(AndLogicalExpr).getAnOperand() = e2 and b2 = true + or + b1 = false and e1.(OrLogicalExpr).getAnOperand() = e2 and b2 = false +} + +/** If `e1` evaluates to `b1` then the subexpression `e2` evaluates to `b2`. */ +predicate exprImplies(Expr e1, boolean b1, Expr e2, boolean b2) { + e1 = e2 and b1 = b2 and (b1 = true or b1 = false) + or + exists(Expr emid, boolean bmid | exprImplies(e1, b1, emid, bmid) and exprImpliesStep(emid, bmid, e2, b2)) +} diff --git a/java/ql/src/semmle/code/java/controlflow/BasicBlocks.qll b/java/ql/src/semmle/code/java/controlflow/BasicBlocks.qll new file mode 100644 index 00000000000..ed1e092c7f3 --- /dev/null +++ b/java/ql/src/semmle/code/java/controlflow/BasicBlocks.qll @@ -0,0 +1,69 @@ +/** + * Provides classes and predicates for working with basic blocks in Java. + */ + +import java +import Dominance +import semmle.code.java.ControlFlowGraph + +/** + * A control-flow node that represents the start of a basic block. + * + * A basic block is a series of nodes with no control-flow branching, which can + * often be treated as a unit in analyses. + */ +class BasicBlock extends ControlFlowNode { + BasicBlock() { + not exists(this.getAPredecessor()) and exists(this.getASuccessor()) or + strictcount(this.getAPredecessor()) > 1 or + exists(ControlFlowNode pred | pred = this.getAPredecessor() | strictcount(pred.getASuccessor()) > 1) + } + + /** Gets an immediate successor of this basic block. */ + cached + BasicBlock getABBSuccessor() { + result = getLastNode().getASuccessor() + } + + /** Gets an immediate predecessor of this basic block. */ + BasicBlock getABBPredecessor() { + result.getABBSuccessor() = this + } + + /** Gets a control-flow node contained in this basic block. */ + ControlFlowNode getANode() { result = getNode(_) } + + /** Gets the control-flow node at a specific (zero-indexed) position in this basic block. */ + cached + ControlFlowNode getNode(int pos) { + result = this and pos = 0 + or + exists(ControlFlowNode mid, int mid_pos | pos = mid_pos + 1 | + getNode(mid_pos) = mid and + mid.getASuccessor() = result and + not result instanceof BasicBlock + ) + } + + /** Gets the first control-flow node in this basic block. */ + ControlFlowNode getFirstNode() { result = this } + + /** Gets the last control-flow node in this basic block. */ + ControlFlowNode getLastNode() { result = getNode(length()-1) } + + /** Gets the number of control-flow nodes contained in this basic block. */ + cached + int length() { result = strictcount(getANode()) } + + /** Holds if this basic block strictly dominates `node`. */ + predicate bbStrictlyDominates(BasicBlock node) { bbStrictlyDominates(this, node) } + + /** Holds if this basic block dominates `node`. (This is reflexive.) */ + predicate bbDominates(BasicBlock node) { bbDominates(this, node) } + + /** Holds if this basic block strictly post-dominates `node`. */ + predicate bbStrictlyPostDominates(BasicBlock node) { bbStrictlyPostDominates(this, node) } + + /** Holds if this basic block post-dominates `node`. (This is reflexive.) */ + predicate bbPostDominates(BasicBlock node) { bbPostDominates(this, node) } +} diff --git a/java/ql/src/semmle/code/java/controlflow/Dominance.qll b/java/ql/src/semmle/code/java/controlflow/Dominance.qll new file mode 100644 index 00000000000..8f36030d812 --- /dev/null +++ b/java/ql/src/semmle/code/java/controlflow/Dominance.qll @@ -0,0 +1,139 @@ +/** + * Provides classes and predicates for control-flow graph dominance. + */ +import java +private import semmle.code.java.ControlFlowGraph + + +/* + * Predicates for basic-block-level dominance. + */ + +/** Entry points for control-flow. */ +private predicate flowEntry(Stmt entry) { + exists(Callable c | entry = c.getBody()) + or + // This disjunct is technically superfluous, but safeguards against extractor problems. + entry instanceof Block and + not exists(entry.getEnclosingCallable()) and + not entry.getParent() instanceof Stmt +} + +/** The successor relation for basic blocks. */ +private predicate bbSucc(BasicBlock pre, BasicBlock post) { + post = pre.getABBSuccessor() +} + +/** The immediate dominance relation for basic blocks. */ +cached predicate bbIDominates(BasicBlock dom, BasicBlock node) = idominance(flowEntry/1, bbSucc/2)(_, dom, node) + +/** Holds if the dominance relation is calculated for `bb`. */ +predicate hasDominanceInformation(BasicBlock bb) { + exists(BasicBlock entry | flowEntry(entry) and bbSucc*(entry, bb)) +} + +/** Exit points for control-flow. */ +private predicate flowExit(Callable exit) { + exists(ControlFlowNode s | s.getASuccessor() = exit) +} + +/** Exit points for basic-block control-flow. */ +private predicate bbSink(BasicBlock exit) { + flowExit(exit.getLastNode()) +} + +/** Reversed `bbSucc`. */ +private predicate bbPred(BasicBlock post, BasicBlock pre) { + post = pre.getABBSuccessor() +} + +/** The immediate post-dominance relation on basic blocks. */ +cached predicate bbIPostDominates(BasicBlock dominator, BasicBlock node) = idominance(bbSink/1,bbPred/2)(_, dominator, node) + +/** Holds if `dom` strictly dominates `node`. */ +predicate bbStrictlyDominates(BasicBlock dom, BasicBlock node) { bbIDominates+(dom, node) } + +/** Holds if `dom` dominates `node`. (This is reflexive.) */ +predicate bbDominates(BasicBlock dom, BasicBlock node) { bbStrictlyDominates(dom, node) or dom = node } + +/** Holds if `dom` strictly post-dominates `node`. */ +predicate bbStrictlyPostDominates(BasicBlock dom, BasicBlock node) { bbIPostDominates+(dom, node) } + +/** Holds if `dom` post-dominates `node`. (This is reflexive.) */ +predicate bbPostDominates(BasicBlock dom, BasicBlock node) { bbStrictlyPostDominates(dom, node) or dom = node } + +/** + * The dominance frontier relation for basic blocks. + * + * This is equivalent to: + * + * ``` + * bbDominates(x, w.getABBPredecessor()) and not bbStrictlyDominates(x, w) + * ``` + */ +predicate dominanceFrontier(BasicBlock x, BasicBlock w) { + x = w.getABBPredecessor() and not bbIDominates(x, w) + or + exists(BasicBlock prev | dominanceFrontier(prev, w) | + bbIDominates(x, prev) and + not bbIDominates(x, w) + ) +} + +/* + * Predicates for expression-level dominance. + */ + +/** Immediate dominance relation on control-flow graph nodes. */ +predicate iDominates(ControlFlowNode dominator, ControlFlowNode node) { + exists(BasicBlock bb, int i | dominator = bb.getNode(i) and node = bb.getNode(i+1)) or + exists(BasicBlock dom, BasicBlock bb | + bbIDominates(dom, bb) and + dominator = dom.getLastNode() and + node = bb.getFirstNode() + ) +} + +/** Holds if `dom` strictly dominates `node`. */ +pragma[inline] +predicate strictlyDominates(ControlFlowNode dom, ControlFlowNode node) { + // This predicate is gigantic, so it must be inlined. + bbStrictlyDominates(dom.getBasicBlock(), node.getBasicBlock()) + or + exists(BasicBlock b, int i, int j | + dom = b.getNode(i) and node = b.getNode(j) and i < j + ) +} + +/** Holds if `dom` dominates `node`. (This is reflexive.) */ +pragma[inline] +predicate dominates(ControlFlowNode dom, ControlFlowNode node) { + // This predicate is gigantic, so it must be inlined. + bbStrictlyDominates(dom.getBasicBlock(), node.getBasicBlock()) + or + exists(BasicBlock b, int i, int j | + dom = b.getNode(i) and node = b.getNode(j) and i <= j + ) +} + +/** Holds if `dom` strictly post-dominates `node`. */ +pragma[inline] +predicate strictlyPostDominates(ControlFlowNode dom, ControlFlowNode node) { + // This predicate is gigantic, so it must be inlined. + bbStrictlyPostDominates(dom.getBasicBlock(), node.getBasicBlock()) + or + exists(BasicBlock b, int i, int j | + dom = b.getNode(i) and node = b.getNode(j) and i > j + ) +} + +/** Holds if `dom` post-dominates `node`. (This is reflexive.) */ +pragma[inline] +predicate postDominates(ControlFlowNode dom, ControlFlowNode node) { + // This predicate is gigantic, so it must be inlined. + bbStrictlyPostDominates(dom.getBasicBlock(), node.getBasicBlock()) + or + exists(BasicBlock b, int i, int j | + dom = b.getNode(i) and node = b.getNode(j) and i >= j + ) +} diff --git a/java/ql/src/semmle/code/java/controlflow/Guards.qll b/java/ql/src/semmle/code/java/controlflow/Guards.qll new file mode 100644 index 00000000000..59e3ed37a67 --- /dev/null +++ b/java/ql/src/semmle/code/java/controlflow/Guards.qll @@ -0,0 +1,233 @@ +import java +private import semmle.code.java.controlflow.Dominance +private import semmle.code.java.controlflow.internal.GuardsLogic + +/** + * A basic block that terminates in a condition, splitting the subsequent control flow. + */ +class ConditionBlock extends BasicBlock { + ConditionBlock() { + this.getLastNode() instanceof ConditionNode + } + + /** Gets the last node of this basic block. */ + ConditionNode getConditionNode() { + result = this.getLastNode() + } + + /** Gets the condition of the last node of this basic block. */ + Expr getCondition() { + result = this.getConditionNode().getCondition() + } + + /** Gets a `true`- or `false`-successor of the last node of this basic block. */ + BasicBlock getTestSuccessor(boolean testIsTrue) { + result = this.getConditionNode().getABranchSuccessor(testIsTrue) + } + + /** + * Holds if `controlled` is a basic block controlled by this condition, that + * is, a basic blocks for which the condition is `testIsTrue`. + */ + predicate controls(BasicBlock controlled, boolean testIsTrue) { + /* + * For this block to control the block `controlled` with `testIsTrue` the following must be true: + * Execution must have passed through the test i.e. `this` must strictly dominate `controlled`. + * Execution must have passed through the `testIsTrue` edge leaving `this`. + * + * Although "passed through the true edge" implies that `this.getATrueSuccessor()` dominates `controlled`, + * the reverse is not true, as flow may have passed through another edge to get to `this.getATrueSuccessor()` + * so we need to assert that `this.getATrueSuccessor()` dominates `controlled` *and* that + * all predecessors of `this.getATrueSuccessor()` are either `this` or dominated by `this.getATrueSuccessor()`. + * + * For example, in the following java snippet: + * ``` + * if (x) + * controlled; + * false_successor; + * uncontrolled; + * ``` + * `false_successor` dominates `uncontrolled`, but not all of its predecessors are `this` (`if (x)`) + * or dominated by itself. Whereas in the following code: + * ``` + * if (x) + * while (controlled) + * also_controlled; + * false_successor; + * uncontrolled; + * ``` + * the block `while controlled` is controlled because all of its predecessors are `this` (`if (x)`) + * or (in the case of `also_controlled`) dominated by itself. + * + * The additional constraint on the predecessors of the test successor implies + * that `this` strictly dominates `controlled` so that isn't necessary to check + * directly. + */ + exists(BasicBlock succ | + succ = this.getTestSuccessor(testIsTrue) and + succ.bbDominates(controlled) and + forall(BasicBlock pred | pred = succ.getABBPredecessor() and pred != this | + succ.bbDominates(pred) + ) + ) + } +} + +/** + * A condition that can be evaluated to either true or false. This can either + * be an `Expr` of boolean type that isn't a boolean literal, or a case of a + * switch statement. + * + * Evaluating a switch case to true corresponds to taking that switch case, and + * evaluating it to false corresponds to taking some other branch. + */ +class Guard extends ExprParent { + Guard() { + this.(Expr).getType() instanceof BooleanType and not this instanceof BooleanLiteral or + this instanceof SwitchCase + } + + /** Gets the immediately enclosing callable whose body contains this guard. */ + Callable getEnclosingCallable() { + result = this.(Expr).getEnclosingCallable() or + result = this.(SwitchCase).getEnclosingCallable() + } + + /** Gets the statement containing this guard. */ + Stmt getEnclosingStmt() { + result = this.(Expr).getEnclosingStmt() or + result = this.(SwitchCase).getSwitch() + } + + /** + * Holds if this guard is an equality test between `e1` and `e2`. The test + * can be either `==`, `!=`, `.equals`, or a switch case. If the test is + * negated, that is `!=`, then `polarity` is false, otherwise `polarity` is + * true. + */ + predicate isEquality(Expr e1, Expr e2, boolean polarity) { + exists(Expr exp1, Expr exp2 | + equalityGuard(this, exp1, exp2, polarity) + | + e1 = exp1.getProperExpr() and e2 = exp2.getProperExpr() or + e2 = exp1.getProperExpr() and e1 = exp2.getProperExpr() + ) + } + + /** + * Holds if the evaluation of this guard to `branch` corresponds to the edge + * from `bb1` to `bb2`. + */ + predicate hasBranchEdge(BasicBlock bb1, BasicBlock bb2, boolean branch) { + exists(ConditionBlock cb | + cb = bb1 and + cb.getCondition() = this and + bb2 = cb.getTestSuccessor(branch) + ) or + exists(SwitchCase sc, ControlFlowNode pred | + sc = this and + branch = true and + bb2.getFirstNode() = sc.getControlFlowNode() and + pred = sc.getControlFlowNode().getAPredecessor() and + pred.(Expr).getParent*() = sc.getSwitch().getExpr() and + bb1 = pred.getBasicBlock() + ) + } + + /** + * Holds if this guard evaluating to `branch` directly controls the block + * `controlled`. That is, the `true`- or `false`-successor of this guard (as + * given by `branch`) dominates `controlled`. + */ + predicate directlyControls(BasicBlock controlled, boolean branch) { + exists(ConditionBlock cb | + cb.getCondition() = this and + cb.controls(controlled, branch) + ) or + switchCaseControls(this, controlled) and branch = true + } + + /** + * Holds if this guard evaluating to `branch` directly or indirectly controls + * the block `controlled`. That is, the evaluation of `controlled` is + * dominated by this guard evaluating to `branch`. + */ + predicate controls(BasicBlock controlled, boolean branch) { + guardControls_v3(this, controlled, branch) + } +} + +private predicate switchCaseControls(SwitchCase sc, BasicBlock bb) { + exists(BasicBlock caseblock, SwitchStmt ss | + ss.getACase() = sc and + caseblock.getFirstNode() = sc.getControlFlowNode() and + caseblock.bbDominates(bb) and + forall(ControlFlowNode pred | pred = sc.getControlFlowNode().getAPredecessor() | + pred.(Expr).getParent*() = ss.getExpr() + ) + ) +} + +/** + * INTERNAL: Use `Guards.controls` instead. + * + * Holds if `guard.controls(controlled, branch)`, except this only relies on + * BaseSSA-based reasoning. + */ +predicate guardControls_v1(Guard guard, BasicBlock controlled, boolean branch) { + guard.directlyControls(controlled, branch) or + exists(Guard g, boolean b | + guardControls_v1(g, controlled, b) and + implies_v1(g, b, guard, branch) + ) +} + +/** + * INTERNAL: Use `Guards.controls` instead. + * + * Holds if `guard.controls(controlled, branch)`, except this doesn't rely on + * RangeAnalysis. + */ +predicate guardControls_v2(Guard guard, BasicBlock controlled, boolean branch) { + guard.directlyControls(controlled, branch) or + exists(Guard g, boolean b | + guardControls_v2(g, controlled, b) and + implies_v2(g, b, guard, branch) + ) +} + +private predicate guardControls_v3(Guard guard, BasicBlock controlled, boolean branch) { + guard.directlyControls(controlled, branch) or + exists(Guard g, boolean b | + guardControls_v3(g, controlled, b) and + implies_v3(g, b, guard, branch) + ) +} + +private predicate equalityGuard(Guard g, Expr e1, Expr e2, boolean polarity) { + exists(EqualityTest eqtest | + eqtest = g and + polarity = eqtest.polarity() and + eqtest.hasOperands(e1, e2) + ) or + exists(MethodAccess ma | + ma = g and + ma.getMethod() instanceof EqualsMethod and + polarity = true and + ma.getAnArgument() = e1 and ma.getQualifier() = e2 + ) or + exists(MethodAccess ma, Method equals | + ma = g and + ma.getMethod() = equals and + polarity = true and + equals.hasName("equals") and + equals.getNumberOfParameters() = 2 and + equals.getDeclaringType().hasQualifiedName("java.util", "Objects") and + ma.getArgument(0) = e1 and ma.getArgument(1) = e2 + ) or + exists(ConstCase cc | + cc = g and + polarity = true and + cc.getSwitch().getExpr().getProperExpr() = e1 and cc.getValue() = e2 + ) +} diff --git a/java/ql/src/semmle/code/java/controlflow/Paths.qll b/java/ql/src/semmle/code/java/controlflow/Paths.qll new file mode 100644 index 00000000000..237b663b74f --- /dev/null +++ b/java/ql/src/semmle/code/java/controlflow/Paths.qll @@ -0,0 +1,85 @@ +/** + * This library provides predicates for reasoning about the set of all paths + * through a callable. + */ + +import java +import semmle.code.java.dispatch.VirtualDispatch + +/** + * A configuration to define an "action". The member predicates + * `callableAlwaysPerformsAction` and `callAlwaysPerformsAction` then gives all + * the callables and calls that always performs an action taking + * inter-procedural flow into account. + */ +abstract class ActionConfiguration extends string { + bindingset[this] + ActionConfiguration() { any() } + + /** Holds if `node` is an action. */ + abstract predicate isAction(ControlFlowNode node); + + /** Holds if every path through `callable` goes through at least one action node. */ + final predicate callableAlwaysPerformsAction(Callable callable) { + callableAlwaysPerformsAction(callable, this) + } + + /** Holds if every path through `call` goes through at least one action node. */ + final predicate callAlwaysPerformsAction(Call call) { + callAlwaysPerformsAction(call, this) + } +} + +/** Gets a `BasicBlock` that contains an action. */ +private BasicBlock actionBlock(ActionConfiguration conf) { + exists(ControlFlowNode node | result = node.getBasicBlock() | + conf.isAction(node) or + callAlwaysPerformsAction(node, conf) + ) +} + +/** Holds if every path through `call` goes through at least one action node. */ +private predicate callAlwaysPerformsAction(Call call, ActionConfiguration conf) { + forex(Callable callable | callable = viableCallable(call) | callableAlwaysPerformsAction(callable, conf)) +} + +/** Holds if an action dominates the exit of the callable. */ +private predicate actionDominatesExit(Callable callable, ActionConfiguration conf) { + exists(BasicBlock exit | + exit.getLastNode() = callable and + actionBlock(conf).bbDominates(exit) + ) +} + +/** Gets a `BasicBlock` that contains an action that does not dominate the exit. */ +private BasicBlock nonDominatingActionBlock(ActionConfiguration conf) { + exists(BasicBlock exit | + result = actionBlock(conf) and + exit.getLastNode() = result.getEnclosingCallable() and + not result.bbDominates(exit) + ) +} + +private class JoinBlock extends BasicBlock { JoinBlock() { 2 <= strictcount(this.getABBPredecessor()) } } + +/** + * Holds if `bb` is a block that is collectively dominated by a set of one or + * more actions that individually does not dominate the exit. + */ +private predicate postActionBlock(BasicBlock bb, ActionConfiguration conf) { + bb = nonDominatingActionBlock(conf) or + if bb instanceof JoinBlock then + forall(BasicBlock pred | pred = bb.getABBPredecessor() | postActionBlock(pred, conf)) + else + postActionBlock(bb.getABBPredecessor(), conf) +} + +/** Holds if every path through `callable` goes through at least one action node. */ +private predicate callableAlwaysPerformsAction(Callable callable, ActionConfiguration conf) { + actionDominatesExit(callable, conf) + or + exists(BasicBlock exit | + exit.getLastNode() = callable and + postActionBlock(exit, conf) + ) +} diff --git a/java/ql/src/semmle/code/java/controlflow/UnreachableBlocks.qll b/java/ql/src/semmle/code/java/controlflow/UnreachableBlocks.qll new file mode 100644 index 00000000000..26423c1c4fc --- /dev/null +++ b/java/ql/src/semmle/code/java/controlflow/UnreachableBlocks.qll @@ -0,0 +1,241 @@ +/** + * Provides classes and predicates for identifying unreachable blocks under a "closed-world" assumption. + */ +import java +import semmle.code.java.controlflow.Guards + +/** + * A field which contains a constant of an immutable type. + * + * This only considers fields which are assigned once. + */ +class ConstantField extends Field { + ConstantField() { + getType() instanceof ImmutableType and + // Assigned once + count(getAnAssignedValue()) = 1 and + /* + * And that assignment is either in the appropriate initializer, or, for instance fields on + * classes with one constructor, in the constructor. + */ + forall(FieldWrite fa | + fa = getAnAccess() + | + if isStatic() then + fa.getEnclosingCallable() instanceof StaticInitializer + else ( + // Defined in the instance initializer. + fa.getEnclosingCallable() instanceof InstanceInitializer or + // It can be defined in the constructor if there is only one constructor. + ( + fa.getEnclosingCallable() instanceof Constructor and + count(getDeclaringType().getAConstructor()) = 1 + ) + ) + ) + } + + /** + * Gets the constant value assigned to the field. + * + * Note: although this value is constant, we may not be able to statically determine the value. + */ + ConstantExpr getConstantValue() { + result = getAnAssignedValue() + } +} + +/** + * A method that returns a single constant value, and is not overridden. + */ +class ConstantMethod extends Method { + ConstantMethod() { + // Just one return statement + count(ReturnStmt rs | rs.getEnclosingCallable() = this) = 1 and + // Which returns a constant expr + exists(ReturnStmt rs | rs.getEnclosingCallable() = this | rs.getResult() instanceof ConstantExpr) and + // And this method is not overridden + not exists(Method m | m.overrides(this)) + } + + /** + * Gets the expression representing the constant value returned. + */ + ConstantExpr getConstantValue() { + exists(ReturnStmt returnStmt | + returnStmt.getEnclosingCallable() = this + | + result = returnStmt.getResult()) + } +} + +/** + * A field that appears constant, but should not be considered constant when determining + * `ConstantExpr`, and, consequently, in the unreachable blocks analysis. + */ +abstract class ExcludedConstantField extends ConstantField { +} + +/** + * An expression that evaluates to a constant at runtime. + * + * This includes all JLS compile-time constants, plus expressions that can be deduced to be + * constant by making a closed world assumption. + */ +class ConstantExpr extends Expr { + ConstantExpr() { + /* + * Ignore reads of excluded fields. + */ + not this.(FieldRead).getField() instanceof ExcludedConstantField and + ( + // A JLS compile time constant expr + this instanceof CompileTimeConstantExpr or + // A call to a constant method + this.(Call).getCallee() instanceof ConstantMethod or + // A read of a constant field + exists(this.(FieldRead).getField().(ConstantField).getConstantValue()) or + // A binary expression where both sides are constant + ( + this.(BinaryExpr).getLeftOperand() instanceof ConstantExpr and + this.(BinaryExpr).getRightOperand() instanceof ConstantExpr + ) or + this.(ParExpr).getExpr() instanceof ConstantExpr + ) + } + + /** + * Gets the inferred boolean value for this constant boolean expression. + */ + boolean getBooleanValue() { + result = this.(CompileTimeConstantExpr).getBooleanValue() or + result = this.(Call).getCallee().(ConstantMethod).getConstantValue().getBooleanValue() or + result = this.(FieldRead).getField().(ConstantField).getConstantValue().getBooleanValue() or + result = this.(ParExpr).getExpr().(ConstantExpr).getBooleanValue() or + // Handle binary expressions that have integer operands and a boolean result. + exists(BinaryExpr b, int left, int right | + b = this and + left = b.getLeftOperand().(ConstantExpr).getIntValue() and + right = b.getRightOperand().(ConstantExpr).getIntValue() + | + (b instanceof LTExpr and if left < right then result = true else result = false) or + (b instanceof LEExpr and if left <= right then result = true else result = false) or + (b instanceof GTExpr and if left > right then result = true else result = false) or + (b instanceof GEExpr and if left >= right then result = true else result = false) or + (b instanceof EQExpr and if left = right then result = true else result = false) or + (b instanceof NEExpr and if left != right then result = true else result = false) + ) + } + + /** + * Gets the inferred int value for this constant int expression. + */ + int getIntValue() { + result = this.(CompileTimeConstantExpr).getIntValue() or + result = this.(Call).getCallee().(ConstantMethod).getConstantValue().getIntValue() or + result = this.(FieldRead).getField().(ConstantField).getConstantValue().getIntValue() + } +} + +/** + * A switch statement that always selects the same case. + */ +class ConstSwitchStmt extends SwitchStmt { + ConstSwitchStmt() { + this.getExpr() instanceof ConstantExpr + } + + /** Gets the `ConstCase` that matches, if any. */ + ConstCase getMatchingConstCase() { + result = getAConstCase() and + // Only handle the int case for now + result.getValue().(ConstantExpr).getIntValue() = getExpr().(ConstantExpr).getIntValue() + } + + /** Gets the matching case, if it can be deduced. */ + SwitchCase getMatchingCase() { + // Must be a value we can deduce + exists(getExpr().(ConstantExpr).getIntValue()) and + if (exists(getMatchingConstCase())) then + result = getMatchingConstCase() + else + result = getDefaultCase() + } + + /** + * Gets a case that never matches. + * + * This only has values if we found the matching case. + */ + SwitchCase getAFailingCase() { + exists(SwitchCase matchingCase | + // We must have found the matching case, otherwise we can't deduce which cases are not matched + matchingCase = getMatchingCase() and + result = getACase() and + result != matchingCase + ) + } +} + +/** + * An unreachable basic block is one that is dominated by a condition that never holds. + */ +class UnreachableBasicBlock extends BasicBlock { + UnreachableBasicBlock() { + /* + * Condition blocks with a constant condition that causes a true/false successor to be + * unreachable. Note: conditions including a single boolean literal e.g. if (false) are not + * modeled as a ConditionBlock - this case is covered by the blocks-without-a-predecessor + * check below. + */ + exists(ConditionBlock conditionBlock, boolean constant | + constant = conditionBlock.getCondition().(ConstantExpr).getBooleanValue() + | + conditionBlock.controls(this, constant.booleanNot()) + ) or + /* + * This block is not reachable in the CFG, and is not a callable, a body of a callable, an + * expression in an annotation, an expression in an assert statement, or a catch clause. + */ + ( + forall(BasicBlock bb | bb = getABBPredecessor() | bb instanceof UnreachableBasicBlock) and + not exists(Callable c | + c.getBody() = this) and + not this instanceof Callable and + not exists(Annotation a | + a.getAChildExpr*() = this + ) and + not exists(AssertStmt a | + a = this.(Expr).getEnclosingStmt() + ) and + not this instanceof CatchClause + ) or + // Switch statements with a constant comparison expression may have unreachable cases. + exists(ConstSwitchStmt constSwitchStmt, BasicBlock failingCaseBlock | + failingCaseBlock = constSwitchStmt.getAFailingCase().getBasicBlock() + | + // Not accessible from the successful case + not constSwitchStmt.getMatchingCase().getBasicBlock().getABBSuccessor*() = failingCaseBlock and + // Blocks dominated by the failing case block are unreachable + constSwitchStmt.getAFailingCase().getBasicBlock().bbDominates(this) + ) + } +} + +/** + * An unreachable expression is an expression contained in an `UnreachableBasicBlock`. + */ +class UnreachableExpr extends Expr { + UnreachableExpr() { + getBasicBlock() instanceof UnreachableBasicBlock + } +} + +/** + * An unreachable statement is a statement contained in an `UnreachableBasicBlock`. + */ +class UnreachableStmt extends Stmt { + UnreachableStmt() { + getBasicBlock() instanceof UnreachableBasicBlock + } +} diff --git a/java/ql/src/semmle/code/java/controlflow/internal/GuardsLogic.qll b/java/ql/src/semmle/code/java/controlflow/internal/GuardsLogic.qll new file mode 100644 index 00000000000..423aa6f42e4 --- /dev/null +++ b/java/ql/src/semmle/code/java/controlflow/internal/GuardsLogic.qll @@ -0,0 +1,306 @@ +/** + * Provides predicates for working with the internal logic of the `Guards` + * library. + */ +import java +import semmle.code.java.controlflow.Guards +private import semmle.code.java.dataflow.SSA +private import semmle.code.java.dataflow.internal.BaseSSA +private import semmle.code.java.dataflow.NullGuards +private import semmle.code.java.dataflow.IntegerGuards + +/** + * Holds if the assumption that `g1` has been evaluated to `b1` implies that + * `g2` has been evaluated to `b2`, that is, the evaluation of `g2` to `b2` + * dominates the evaluation of `g1` to `b1`. + * + * Restricted to BaseSSA-based reasoning. + */ +predicate implies_v1(Guard g1, boolean b1, Guard g2, boolean b2) { + g1.(ParExpr).getExpr() = g2 and b1 = b2 and (b1 = true or b1 = false) or + g1.(AndBitwiseExpr).getAnOperand() = g2 and b1 = true and b2 = true or + g1.(OrBitwiseExpr).getAnOperand() = g2 and b1 = false and b2 = false or + g1.(AndLogicalExpr).getAnOperand() = g2 and b1 = true and b2 = true or + g1.(OrLogicalExpr).getAnOperand() = g2 and b1 = false and b2 = false or + g1.(LogNotExpr).getExpr() = g2 and b1 = b2.booleanNot() and (b1 = true or b1 = false) or + exists(EqualityTest eqtest, boolean polarity, BooleanLiteral boollit | + eqtest = g1 and + eqtest.hasOperands(g2, boollit) and + eqtest.polarity() = polarity and + (b1 = true or b1 = false) and + b2 = b1.booleanXor(polarity).booleanXor(boollit.getBooleanValue()) + ) or + exists(ConditionalExpr cond, boolean branch, BooleanLiteral boollit, boolean boolval | + cond.getTrueExpr() = boollit and branch = true or + cond.getFalseExpr() = boollit and branch = false + | + cond = g1 and + boolval = boollit.getBooleanValue() and + b1 = boolval.booleanNot() and + ( + g2 = cond.getCondition() and b2 = branch.booleanNot() or + g2 = cond.getTrueExpr() and b2 = b1 or + g2 = cond.getFalseExpr() and b2 = b1 + ) + ) or + g1.(DefaultCase).getSwitch().getAConstCase() = g2 and b1 = true and b2 = false or + exists(BaseSsaUpdate vbool | + vbool.getAUse() = g1 and + vbool.getDefiningExpr().(VariableAssign).getSource() = g2 and + (b1 = true or b1 = false) and + b2 = b1 + ) +} + +/** + * Holds if the assumption that `g1` has been evaluated to `b1` implies that + * `g2` has been evaluated to `b2`, that is, the evaluation of `g2` to `b2` + * dominates the evaluation of `g1` to `b1`. + * + * Allows full use of SSA but is restricted to pre-RangeAnalysis reasoning. + */ +predicate implies_v2(Guard g1, boolean b1, Guard g2, boolean b2) { + implies_v1(g1, b1, g2, b2) or + exists(SsaExplicitUpdate vbool | + vbool.getAUse() = g1 and + vbool.getDefiningExpr().(VariableAssign).getSource() = g2 and + (b1 = true or b1 = false) and + b2 = b1 + ) or + exists(SsaVariable v, AbstractValue k | + // If `v = g2 ? k : ...` or `v = g2 ? ... : k` then a guard + // proving `v != k` ensures that `g2` evaluates to `b2`. + conditionalAssignVal(v, g2, b2.booleanNot(), k) and + guardImpliesNotEqual1(g1, b1, v, k) + ) or + exists(SsaVariable v, Expr e, AbstractValue k | + // If `v = g2 ? k : ...` and all other assignments to `v` are different from + // `k` then a guard proving `v == k` ensures that `g2` evaluates to `b2`. + uniqueValue(v, e, k) and + guardImpliesEqual(g1, b1, v, k) and + g2.directlyControls(e.getBasicBlock(), b2) and + not g2.directlyControls(getBasicBlockOfGuard(g1), b2) + ) +} + +/** + * Holds if the assumption that `g1` has been evaluated to `b1` implies that + * `g2` has been evaluated to `b2`, that is, the evaluation of `g2` to `b2` + * dominates the evaluation of `g1` to `b1`. + */ +cached +predicate implies_v3(Guard g1, boolean b1, Guard g2, boolean b2) { + implies_v2(g1, b1, g2, b2) or + exists(SsaVariable v, AbstractValue k | + // If `v = g2 ? k : ...` or `v = g2 ? ... : k` then a guard + // proving `v != k` ensures that `g2` evaluates to `b2`. + conditionalAssignVal(v, g2, b2.booleanNot(), k) and + guardImpliesNotEqual2(g1, b1, v, k) + ) or + exists(SsaVariable v | + conditionalAssign(v, g2, b2.booleanNot(), clearlyNotNullExpr()) and + guardImpliesEqual(g1, b1, v, TAbsValNull()) + ) +} + +private newtype TAbstractValue = + TAbsValNull() or + TAbsValInt(int i) { exists(CompileTimeConstantExpr c | c.getIntValue() = i) } or + TAbsValChar(string c) { exists(CharacterLiteral lit | lit.getValue() = c) } or + TAbsValString(string s) { exists(StringLiteral lit | lit.getValue() = s) } or + TAbsValEnum(EnumConstant c) + +/** The value of a constant expression. */ +private abstract class AbstractValue extends TAbstractValue { + abstract string toString(); + /** Gets an expression whose value is this abstract value. */ + abstract Expr getExpr(); +} + +private class AbsValNull extends AbstractValue, TAbsValNull { + override string toString() { result = "null" } + override Expr getExpr() { result = alwaysNullExpr() } +} + +private class AbsValInt extends AbstractValue, TAbsValInt { + int i; + AbsValInt() { this = TAbsValInt(i) } + override string toString() { result = i.toString() } + override Expr getExpr() { result.(CompileTimeConstantExpr).getIntValue() = i } +} + +private class AbsValChar extends AbstractValue, TAbsValChar { + string c; + AbsValChar() { this = TAbsValChar(c) } + override string toString() { result = c } + override Expr getExpr() { result.(CharacterLiteral).getValue() = c } +} + +private class AbsValString extends AbstractValue, TAbsValString { + string s; + AbsValString() { this = TAbsValString(s) } + override string toString() { result = s } + override Expr getExpr() { result.(CompileTimeConstantExpr).getStringValue() = s } +} + +private class AbsValEnum extends AbstractValue, TAbsValEnum { + EnumConstant c; + AbsValEnum() { this = TAbsValEnum(c) } + override string toString() { result = c.toString() } + override Expr getExpr() { result = c.getAnAccess() } +} + +/** + * Holds if `v` can have a value that is not representable as an `AbstractValue`. + */ +private predicate hasPossibleUnknownValue(SsaVariable v) { + exists(SsaVariable def | v.getAnUltimateDefinition() = def | + def instanceof SsaImplicitUpdate or + def instanceof SsaImplicitInit or + exists(VariableUpdate upd | upd = def.(SsaExplicitUpdate).getDefiningExpr() | + not exists(upd.(VariableAssign).getSource()) + ) or + exists(VariableAssign a, Expr e | + a = def.(SsaExplicitUpdate).getDefiningExpr() and + e = possibleValue(a.getSource()) and + not exists(AbstractValue val | val.getExpr() = e) + ) + ) +} + +/** + * Gets a sub-expression of `e` whose value can flow to `e` through + * `ConditionalExpr`s. Parentheses are also removed. + */ +private Expr possibleValue(Expr e) { + result = possibleValue(e.(ParExpr).getExpr()) or + result = possibleValue(e.(ConditionalExpr).getTrueExpr()) or + result = possibleValue(e.(ConditionalExpr).getFalseExpr()) or + result = e and not e instanceof ParExpr and not e instanceof ConditionalExpr +} + +/** + * Gets an ultimate definition of `v` that is not itself a phi node. The + * boolean `fromBackEdge` indicates whether the flow from `result` to `v` goes + * through a back edge. + */ +SsaVariable getADefinition(SsaVariable v, boolean fromBackEdge) { + result = v and not v instanceof SsaPhiNode and fromBackEdge = false or + exists(SsaVariable inp, BasicBlock bb, boolean fbe | + v.(SsaPhiNode).hasInputFromBlock(inp, bb) and + result = getADefinition(inp, fbe) and + (if v.getBasicBlock().bbDominates(bb) then fromBackEdge = true else fromBackEdge = fbe) + ) +} + +/** + * Holds if `e` equals `k` and may be assigned to `v`. The boolean + * `fromBackEdge` indicates whether the flow from `e` to `v` goes through a + * back edge. + */ +private predicate possibleValue(SsaVariable v, boolean fromBackEdge, Expr e, AbstractValue k) { + not hasPossibleUnknownValue(v) and + exists(SsaExplicitUpdate def | + def = getADefinition(v, fromBackEdge) and + e = possibleValue(def.getDefiningExpr().(VariableAssign).getSource().getProperExpr()) and + k.getExpr() = e + ) +} + +/** + * Holds if `e` equals `k` and may be assigned to `v` without going through + * back edges, and all other possible ultimate definitions of `v` are different + * from `k`. The trivial case where `v` is an `SsaExplicitUpdate` with `e` as + * the only possible value is excluded. + */ +private predicate uniqueValue(SsaVariable v, Expr e, AbstractValue k) { + possibleValue(v, false, e, k) and + forex(Expr other, AbstractValue otherval | possibleValue(v, _, other, otherval) and other != e | otherval != k) +} + +/** + * Holds if `guard` evaluating to `branch` implies that `v` equals `k`. + */ +private predicate guardImpliesEqual(Guard guard, boolean branch, SsaVariable v, AbstractValue k) { + guard.isEquality(v.getAUse(), k.getExpr(), branch) +} + +private BasicBlock getBasicBlockOfGuard(Guard g) { + result = g.(Expr).getControlFlowNode().getBasicBlock() or + result = g.(SwitchCase).getSwitch().getExpr().getProperExpr().getControlFlowNode().getBasicBlock() +} + +private ControlFlowNode getAGuardBranchSuccessor(Guard g, boolean branch) { + result = g.(Expr).getControlFlowNode().(ConditionNode).getABranchSuccessor(branch) or + result = g.(SwitchCase).getControlFlowNode() and branch = true +} + +/** + * Holds if `v` is conditionally assigned `e` under the condition that `guard` evaluates to `branch`. + */ +private predicate conditionalAssign(SsaVariable v, Guard guard, boolean branch, Expr e) { + exists(ConditionalExpr c | + v.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource().getProperExpr() = c and + guard = c.getCondition().getProperExpr() + | + branch = true and e = c.getTrueExpr().getProperExpr() or + branch = false and e = c.getFalseExpr().getProperExpr() + ) or + exists(SsaExplicitUpdate upd, SsaPhiNode phi | + guard.directlyControls(upd.getBasicBlock(), branch) and + upd.getDefiningExpr().(VariableAssign).getSource().getProperExpr() = e and + phi = v and + upd = phi.getAPhiInput() and + getBasicBlockOfGuard(guard).bbStrictlyDominates(phi.getBasicBlock()) and + not guard.directlyControls(phi.getBasicBlock(), branch) and + forall(SsaVariable other | other != upd and other = phi.getAPhiInput() | + guard.directlyControls(other.getBasicBlock(), branch.booleanNot()) or + other.getBasicBlock().bbDominates(getBasicBlockOfGuard(guard)) and + not other.isLiveAtEndOfBlock(getAGuardBranchSuccessor(guard, branch)) + ) + ) +} + +/** + * Holds if `v` is conditionally assigned `val` under the condition that `guard` evaluates to `branch`. + */ +private predicate conditionalAssignVal(SsaVariable v, Guard guard, boolean branch, AbstractValue val) { + conditionalAssign(v, guard, branch, val.getExpr()) +} + +private predicate relevantEq(SsaVariable v, AbstractValue val) { + conditionalAssignVal(v, _, _, val) +} + +/** + * Holds if the evaluation of `guard` to `branch` implies that `v` does not have the value `val`. + */ +private predicate guardImpliesNotEqual1(Guard guard, boolean branch, SsaVariable v, AbstractValue val) { + relevantEq(v, val) and + ( + guard.isEquality(v.getAUse(), val.getExpr(), branch.booleanNot()) + or + exists(AbstractValue val2 | + guard.isEquality(v.getAUse(), val2.getExpr(), branch) and + val != val2 + ) + or + guard.(InstanceOfExpr).getExpr() = sameValue(v, _) and branch = true and val = TAbsValNull() + ) +} + +/** + * Holds if the evaluation of `guard` to `branch` implies that `v` does not have the value `val`. + */ +private predicate guardImpliesNotEqual2(Guard guard, boolean branch, SsaVariable v, AbstractValue val) { + relevantEq(v, val) and + ( + guard = directNullGuard(v, branch, false) and val = TAbsValNull() + or + exists(int k | + guard = integerGuard(v.getAUse(), branch, k, false) and + val = TAbsValInt(k) + ) + ) +} + diff --git a/java/ql/src/semmle/code/java/controlflow/unreachableblocks/ExcludeDebuggingProfilingLogging.qll b/java/ql/src/semmle/code/java/controlflow/unreachableblocks/ExcludeDebuggingProfilingLogging.qll new file mode 100644 index 00000000000..444eb93bb9a --- /dev/null +++ b/java/ql/src/semmle/code/java/controlflow/unreachableblocks/ExcludeDebuggingProfilingLogging.qll @@ -0,0 +1,39 @@ +import java +import semmle.code.java.controlflow.UnreachableBlocks + +/** + * Exclude from the unreachable block analysis constant fields that look like they are flags for + * controlling debugging, profiling or logging features. + * + * Debugging, profiling and logging flags that are compile time constants are usually intended to be + * toggled by the developer at compile time to provide extra information when developing the + * application, or when triaging a problem. By including this sub-class, blocks that are unreachable + * because they are guarded by a check of such a flag are considered reachable. + * + * Note: we explicitly limit this to debugging, profiling and logging flags. True feature toggles + * are treated as constant true/false, because it is much less likely that they are toggled in + * practice. + */ +class ExcludeDebuggingProfilingLogging extends ExcludedConstantField { + ExcludeDebuggingProfilingLogging() { + exists(string validFieldName | + validFieldName = "debug" or + validFieldName = "profiling" or + validFieldName = "profile" or + validFieldName = "time" or + validFieldName = "verbose" or + validFieldName = "report" or + validFieldName = "dbg" or + validFieldName = "timing" or + validFieldName = "assert" or + validFieldName = "log" + | + getName().regexpMatch(".*(?i)" + validFieldName + ".*") + ) and + // Boolean type + ( + getType().hasName("boolean") or + getType().(BoxedType).hasQualifiedName("java.lang", "Boolean") + ) + } +} diff --git a/java/ql/src/semmle/code/java/dataflow/DataFlow.qll b/java/ql/src/semmle/code/java/dataflow/DataFlow.qll new file mode 100644 index 00000000000..fec7ba422ec --- /dev/null +++ b/java/ql/src/semmle/code/java/dataflow/DataFlow.qll @@ -0,0 +1,33 @@ +/** + * Provides classes for performing local (intra-procedural) and + * global (inter-procedural) data flow analyses. + */ + +import java + +module DataFlow { + import semmle.code.java.dataflow.internal.DataFlowImpl + + /** + * This class exists to prevent mutual recursion between the user-overridden + * member predicates of `Configuration` and the rest of the data-flow library. + * Good performance cannot be guaranteed in the presence of such recursion, so + * it should be replaced by using more than one copy of the data flow library. + * Four copies are available: `DataFlow` through `DataFlow4`. + */ + private abstract + class ConfigurationRecursionPrevention extends Configuration { + bindingset[this] + ConfigurationRecursionPrevention() { any() } + + override predicate hasFlow(Node source, Node sink) { + strictcount(Node n | this.isSource(n)) < 0 + or + strictcount(Node n | this.isSink(n)) < 0 + or + strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 + or + super.hasFlow(source, sink) + } + } +} diff --git a/java/ql/src/semmle/code/java/dataflow/DataFlow2.qll b/java/ql/src/semmle/code/java/dataflow/DataFlow2.qll new file mode 100644 index 00000000000..93cd4e6dbe4 --- /dev/null +++ b/java/ql/src/semmle/code/java/dataflow/DataFlow2.qll @@ -0,0 +1,33 @@ +/** + * Provides classes for performing local (intra-procedural) and + * global (inter-procedural) data flow analyses. + */ + +import java + +module DataFlow2 { + import semmle.code.java.dataflow.internal.DataFlowImpl2 + + /** + * This class exists to prevent mutual recursion between the user-overridden + * member predicates of `Configuration` and the rest of the data-flow library. + * Good performance cannot be guaranteed in the presence of such recursion, so + * it should be replaced by using more than one copy of the data flow library. + * Four copies are available: `DataFlow` through `DataFlow4`. + */ + private abstract + class ConfigurationRecursionPrevention extends Configuration { + bindingset[this] + ConfigurationRecursionPrevention() { any() } + + override predicate hasFlow(Node source, Node sink) { + strictcount(Node n | this.isSource(n)) < 0 + or + strictcount(Node n | this.isSink(n)) < 0 + or + strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 + or + super.hasFlow(source, sink) + } + } +} diff --git a/java/ql/src/semmle/code/java/dataflow/DataFlow3.qll b/java/ql/src/semmle/code/java/dataflow/DataFlow3.qll new file mode 100644 index 00000000000..0f68a15f45d --- /dev/null +++ b/java/ql/src/semmle/code/java/dataflow/DataFlow3.qll @@ -0,0 +1,33 @@ +/** + * Provides classes for performing local (intra-procedural) and + * global (inter-procedural) data flow analyses. + */ + +import java + +module DataFlow3 { + import semmle.code.java.dataflow.internal.DataFlowImpl3 + + /** + * This class exists to prevent mutual recursion between the user-overridden + * member predicates of `Configuration` and the rest of the data-flow library. + * Good performance cannot be guaranteed in the presence of such recursion, so + * it should be replaced by using more than one copy of the data flow library. + * Four copies are available: `DataFlow` through `DataFlow4`. + */ + private abstract + class ConfigurationRecursionPrevention extends Configuration { + bindingset[this] + ConfigurationRecursionPrevention() { any() } + + override predicate hasFlow(Node source, Node sink) { + strictcount(Node n | this.isSource(n)) < 0 + or + strictcount(Node n | this.isSink(n)) < 0 + or + strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 + or + super.hasFlow(source, sink) + } + } +} diff --git a/java/ql/src/semmle/code/java/dataflow/DataFlow4.qll b/java/ql/src/semmle/code/java/dataflow/DataFlow4.qll new file mode 100644 index 00000000000..29b8d97c616 --- /dev/null +++ b/java/ql/src/semmle/code/java/dataflow/DataFlow4.qll @@ -0,0 +1,33 @@ +/** + * Provides classes for performing local (intra-procedural) and + * global (inter-procedural) data flow analyses. + */ + +import java + +module DataFlow4 { + import semmle.code.java.dataflow.internal.DataFlowImpl4 + + /** + * This class exists to prevent mutual recursion between the user-overridden + * member predicates of `Configuration` and the rest of the data-flow library. + * Good performance cannot be guaranteed in the presence of such recursion, so + * it should be replaced by using more than one copy of the data flow library. + * Four copies are available: `DataFlow` through `DataFlow4`. + */ + private abstract + class ConfigurationRecursionPrevention extends Configuration { + bindingset[this] + ConfigurationRecursionPrevention() { any() } + + override predicate hasFlow(Node source, Node sink) { + strictcount(Node n | this.isSource(n)) < 0 + or + strictcount(Node n | this.isSink(n)) < 0 + or + strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 + or + super.hasFlow(source, sink) + } + } +} diff --git a/java/ql/src/semmle/code/java/dataflow/DataFlow5.qll b/java/ql/src/semmle/code/java/dataflow/DataFlow5.qll new file mode 100644 index 00000000000..2d2c5afc2af --- /dev/null +++ b/java/ql/src/semmle/code/java/dataflow/DataFlow5.qll @@ -0,0 +1,33 @@ +/** + * Provides classes for performing local (intra-procedural) and + * global (inter-procedural) data flow analyses. + */ + +import java + +module DataFlow5 { + import semmle.code.java.dataflow.internal.DataFlowImpl5 + + /** + * This class exists to prevent mutual recursion between the user-overridden + * member predicates of `Configuration` and the rest of the data-flow library. + * Good performance cannot be guaranteed in the presence of such recursion, so + * it should be replaced by using more than one copy of the data flow library. + * Four copies are available: `DataFlow` through `DataFlow4`. + */ + private abstract + class ConfigurationRecursionPrevention extends Configuration { + bindingset[this] + ConfigurationRecursionPrevention() { any() } + + override predicate hasFlow(Node source, Node sink) { + strictcount(Node n | this.isSource(n)) < 0 + or + strictcount(Node n | this.isSink(n)) < 0 + or + strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 + or + super.hasFlow(source, sink) + } + } +} diff --git a/java/ql/src/semmle/code/java/dataflow/DefUse.qll b/java/ql/src/semmle/code/java/dataflow/DefUse.qll new file mode 100644 index 00000000000..11af7d88c7e --- /dev/null +++ b/java/ql/src/semmle/code/java/dataflow/DefUse.qll @@ -0,0 +1,54 @@ +/** + * Provides classes and predicates for def-use and use-use pairs. Built on top of the SSA library for + * maximal precision. + */ + +import java +private import SSA + +/** + * Holds if `use1` and `use2` form a use-use-pair of the same SSA variable, + * that is, the value read in `use1` can reach `use2` without passing through + * any SSA definition of the variable. + * + * This is the transitive closure of `adjacentUseUseSameVar`. + */ +predicate useUsePairSameVar(RValue use1, RValue use2) { + adjacentUseUseSameVar+(use1, use2) +} + +/** + * Holds if `use1` and `use2` form a use-use-pair of the same + * `SsaSourceVariable`, that is, the value read in `use1` can reach `use2` + * without passing through any SSA definition of the variable except for phi + * nodes and uncertain implicit updates. + * + * This is the transitive closure of `adjacentUseUse`. + */ +predicate useUsePair(RValue use1, RValue use2) { + adjacentUseUse+(use1, use2) +} + +/** + * Holds if there exists a path from `def` to `use` without passing through another + * `VariableUpdate` of the `LocalScopeVariable` that they both refer to. + * + * Other paths may also exist, so the SSA variables in `def` and `use` can be different. + */ +predicate defUsePair(VariableUpdate def, RValue use) { + exists(SsaVariable v | + v.getAUse() = use and v.getAnUltimateDefinition().(SsaExplicitUpdate).getDefiningExpr() = def + ) +} + +/** + * Holds if there exists a path from the entry-point of the callable to `use` without + * passing through a `VariableUpdate` of the parameter `p` that `use` refers to. + * + * Other paths may also exist, so the SSA variables can be different. + */ +predicate parameterDefUsePair(Parameter p, RValue use) { + exists(SsaVariable v | + v.getAUse() = use and v.getAnUltimateDefinition().(SsaImplicitInit).isParameterDefinition(p) + ) +} diff --git a/java/ql/src/semmle/code/java/dataflow/FlowSources.qll b/java/ql/src/semmle/code/java/dataflow/FlowSources.qll new file mode 100644 index 00000000000..85f4d8677e7 --- /dev/null +++ b/java/ql/src/semmle/code/java/dataflow/FlowSources.qll @@ -0,0 +1,225 @@ +import java +import semmle.code.java.dataflow.DataFlow +import semmle.code.java.dataflow.TaintTracking +import semmle.code.java.dataflow.DefUse +import semmle.code.java.frameworks.Jdbc +import semmle.code.java.frameworks.Networking +import semmle.code.java.frameworks.Properties +import semmle.code.java.frameworks.Rmi +import semmle.code.java.frameworks.Servlets +import semmle.code.java.frameworks.ApacheHttp +import semmle.code.java.frameworks.android.XmlParsing +import semmle.code.java.frameworks.android.WebView +import semmle.code.java.frameworks.JaxWS +import semmle.code.java.frameworks.android.Intent + +/** Class for `tainted` user input. */ +abstract class UserInput extends DataFlow::Node {} + +private predicate variableStep(Expr tracked, VarAccess sink) { + exists(VariableAssign def | + def.getSource() = tracked and + defUsePair(def, sink) + ) +} + +/** Input that may be controlled by a remote user. */ +class RemoteUserInput extends UserInput { + RemoteUserInput() { + this.asExpr().(MethodAccess).getMethod() instanceof RemoteTaintedMethod + or + // Parameters to RMI methods. + exists(RemoteCallableMethod method | + method.getAParameter() = this.asParameter() and + ( + getType() instanceof PrimitiveType or + getType() instanceof TypeString + ) + ) + or + // Parameters to Jax WS methods. + exists(JaxWsEndpoint endpoint | + endpoint.getARemoteMethod().getAParameter() = this.asParameter() + ) + or + // Parameters to Jax Rs methods. + exists(JaxRsResourceClass service | + service.getAnInjectableCallable().getAParameter() = this.asParameter() or + service.getAnInjectableField().getAnAccess() = this.asExpr() + ) + or + // Reverse DNS. Try not to trigger on `localhost`. + exists(MethodAccess m | m = this.asExpr() | + m.getMethod() instanceof ReverseDNSMethod and + not exists(MethodAccess l | + (variableStep(l, m.getQualifier()) or l = m.getQualifier()) and + l.getMethod().getName() = "getLocalHost" + ) + ) + or + //MessageBodyReader + exists(MessageBodyReaderRead m | + m.getParameter(4) = this.asParameter() or + m.getParameter(5) = this.asParameter() + ) + } + + /** + * DEPRECATED: Use a configuration with a defined sink instead. + * + * Holds if taint can flow from this `RemoteUserInput` to `sink`. + * + * In addition to the basic taint flow, this allows a path to end in a number + * of steps through instance fields. + */ + deprecated + predicate flowsTo(DataFlow::Node sink) { + remoteUserInputFlow(this, sink) + } +} + +/** + * Holds if taint can flow from `node1` to `node2` in either one local step or + * through an instance field. + */ +private predicate localInstanceFieldStep(DataFlow::Node node1, DataFlow::Node node2) { + TaintTracking::localTaintStep(node1, node2) or + exists(InstanceField field | + node1.asExpr() = field.getAnAssignedValue() or + exists(Assignment assign | assign.getRhs() = node1.asExpr() | + assign.getDest().(ArrayAccess).getArray() = field.getAnAccess() + ) + | + node2.asExpr() = field.getAnAccess() + ) +} + +private module RemoteUserInputFlow { + private import semmle.code.java.dataflow.internal.DataFlowImplDepr + private import semmle.code.java.security.SecurityTests + private import semmle.code.java.security.Validation + + deprecated + class RemoteUserInputConfig extends Configuration { + RemoteUserInputConfig() { this = "FlowSources.qll:RemoteUserInputConfig" } + override + predicate isSource(DataFlow::Node source) { source instanceof RemoteUserInput } + override + predicate isSink(DataFlow::Node sink) { any() } + override int fieldFlowBranchLimit() { result = 0 } + override predicate isBarrier(DataFlow::Node node) { + // Ignore paths through test code. + node.getEnclosingCallable().getDeclaringType() instanceof NonSecurityTestClass or + exists(ValidatedVariable var | node.asExpr() = var.getAnAccess()) + } + override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + TaintTracking::localAdditionalTaintStep(node1, node2) + } + } +} + +cached +deprecated +private predicate remoteUserInputFlow(RemoteUserInput src, DataFlow::Node sink) { + any(RemoteUserInputFlow::RemoteUserInputConfig config).hasFlow(src, sink) or + exists(DataFlow::Node mid | + remoteUserInputFlow(src, mid) and + localInstanceFieldStep(mid, sink) + ) +} + +/** Input that may be controlled by a local user. */ +abstract class LocalUserInput extends UserInput {} + +class EnvInput extends LocalUserInput { + EnvInput() { + // Parameters to a main method. + exists(MainMethod main | this.asParameter() = main.getParameter(0)) + or + // Args4j arguments. + exists(Field f | this.asExpr() = f.getAnAccess() | f.getAnAnnotation().getType().getQualifiedName() = "org.kohsuke.args4j.Argument") + or + // Results from various specific methods. + this.asExpr().(MethodAccess).getMethod() instanceof EnvTaintedMethod + or + // Access to `System.in`. + exists(Field f | this.asExpr() = f.getAnAccess() | f instanceof SystemIn) + or + // Access to files. + this.asExpr().(ConstructorCall).getConstructedType().hasQualifiedName("java.io", "FileInputStream") + } +} + +class DatabaseInput extends LocalUserInput { + DatabaseInput() { + this.asExpr().(MethodAccess).getMethod() instanceof ResultSetGetStringMethod + } +} + +private +class RemoteTaintedMethod extends Method { + RemoteTaintedMethod() { + this instanceof ServletRequestGetParameterMethod or + this instanceof ServletRequestGetParameterMapMethod or + this instanceof ServletRequestGetParameterNamesMethod or + this instanceof HttpServletRequestGetQueryStringMethod or + this instanceof HttpServletRequestGetHeaderMethod or + this instanceof HttpServletRequestGetPathMethod or + this instanceof HttpServletRequestGetHeadersMethod or + this instanceof HttpServletRequestGetHeaderNamesMethod or + this instanceof HttpServletRequestGetRequestURIMethod or + this instanceof HttpServletRequestGetRequestURLMethod or + this instanceof HttpServletRequestGetRemoteUserMethod or + this instanceof ServletRequestGetBodyMethod or + this instanceof CookieGetValueMethod or + this instanceof CookieGetNameMethod or + this instanceof CookieGetCommentMethod or + this instanceof URLConnectionGetInputStreamMethod or + this instanceof SocketGetInputStreamMethod or + this instanceof ApacheHttpGetParams or + this instanceof ApacheHttpEntityGetContent or + // In the setting of Android we assume that XML has been transmitted over + // the network, so may be tainted. + this instanceof XmlPullGetMethod or + this instanceof XmlAttrSetGetMethod or + // The current URL in a browser may be untrusted or uncontrolled. + this instanceof WebViewGetUrlMethod + } +} + +private +class EnvTaintedMethod extends Method { + EnvTaintedMethod() { + this instanceof MethodSystemGetenv or + this instanceof PropertiesGetPropertyMethod or + this instanceof MethodSystemGetProperty + } +} + +class TypeInetAddr extends RefType { + TypeInetAddr() { + this.getQualifiedName() = "java.net.InetAddress" + } +} + +class ReverseDNSMethod extends Method { + ReverseDNSMethod() { + this.getDeclaringType() instanceof TypeInetAddr and + ( + this.getName() = "getHostName" or + this.getName() = "getCanonicalHostName" + ) + } +} + +/** Android `Intent` that may have come from a hostile application. */ +class AndroidIntentInput extends DataFlow::Node { + AndroidIntentInput() { + exists(MethodAccess ma, AndroidGetIntentMethod m | ma.getMethod().overrides*(m) and + this.asExpr() = ma + ) or + exists(Method m, AndroidReceiveIntentMethod rI | m.overrides*(rI) and + this.asParameter() = m.getParameter(1) + ) + } +} diff --git a/java/ql/src/semmle/code/java/dataflow/Guards.qll b/java/ql/src/semmle/code/java/dataflow/Guards.qll new file mode 100644 index 00000000000..90685bfbfac --- /dev/null +++ b/java/ql/src/semmle/code/java/dataflow/Guards.qll @@ -0,0 +1,65 @@ +import java +private import semmle.code.java.controlflow.Guards as Guards +private import semmle.code.java.controlflow.Dominance + +/** + * DEPRECATED: Use semmle.code.java.controlflow.Guards instead. + * + * A basic block that terminates in a condition, splitting the subsequent control flow. + */ +deprecated +class ConditionBlock = Guards::ConditionBlock; + +/** Holds if `n` updates the locally scoped variable `v`. */ +deprecated +predicate variableUpdate(ControlFlowNode n, LocalScopeVariable v) { + exists(VariableUpdate a | a = n | a.getDestVar() = v) +} + +/** Holds if `bb` updates the locally scoped variable `v`. */ +deprecated private predicate variableUpdateBB(BasicBlock bb, LocalScopeVariable v) { + variableUpdate(bb.getANode(), v) +} + +/** Indicates the position of phi-nodes in an SSA representation. */ +deprecated private predicate needPhiNode(BasicBlock bb, LocalScopeVariable v) { + exists(BasicBlock def | dominanceFrontier(def, bb) | + variableUpdateBB(def, v) or needPhiNode(def, v) + ) +} + +/** Locally scoped variable `v` occurs in the condition of `cb`. */ +deprecated private predicate relevantVar(ConditionBlock cb, LocalScopeVariable v) { + v.getAnAccess() = cb.getCondition().getAChildExpr*() +} + +/** Blocks controlled by the condition in `cb` for which `v` is unchanged. */ +deprecated private predicate controlsBlockWithSameVar(ConditionBlock cb, boolean testIsTrue, LocalScopeVariable v, BasicBlock controlled) { + cb.controls(controlled, testIsTrue) and + relevantVar(cb, v) and + not needPhiNode(controlled, v) and + ( + controlled = cb.getTestSuccessor(testIsTrue) + or + exists(BasicBlock mid | + controlsBlockWithSameVar(cb, testIsTrue, v, mid) and + not variableUpdateBB(mid, v) and + controlled = mid.getABBSuccessor() + ) + ) +} + +/** + * DEPRECATED: Use semmle.code.java.dataflow.SSA instead. + * + * Statements controlled by the condition in `s` for which `v` is unchanged (`v` is the same SSA + * variable in both `s` and `controlled`). The condition in `s` must contain an access of `v`. + */ +deprecated +predicate controlsNodeWithSameVar(ConditionNode cn, boolean testIsTrue, LocalScopeVariable v, ControlFlowNode controlled) { + exists(ConditionBlock cb, BasicBlock controlledBB, int i | + cb.getConditionNode() = cn and + controlsBlockWithSameVar(cb, testIsTrue, v, controlledBB) and + controlled = controlledBB.getNode(i) and + not exists(ControlFlowNode update, int j | update = controlledBB.getNode(j) and j < i and variableUpdate(update, v))) +} diff --git a/java/ql/src/semmle/code/java/dataflow/InstanceAccess.qll b/java/ql/src/semmle/code/java/dataflow/InstanceAccess.qll new file mode 100644 index 00000000000..4ac73ebbaf5 --- /dev/null +++ b/java/ql/src/semmle/code/java/dataflow/InstanceAccess.qll @@ -0,0 +1,259 @@ +/** + * Provides classes and predicates for reasoning about explicit and implicit + * instance accesses. + */ +import java + +/** + * Holds if `cc` constructs an inner class that holds a reference to its + * enclosing class `t` and the enclosing instance is not given explicitly as a + * qualifier of the constructor. + */ +private predicate implicitSetEnclosingInstance(ConstructorCall cc, RefType t) { + exists(InnerClass ic | + ic = cc.getConstructedType().getSourceDeclaration() and + ic.hasEnclosingInstance() and + ic.getEnclosingType() = t and + not cc instanceof ThisConstructorInvocationStmt and + not exists(cc.getQualifier()) + ) +} + +/** + * Holds if `cc` implicitly sets the enclosing instance of the constructed + * inner class to `this`. + */ +private predicate implicitSetEnclosingInstanceToThis(ConstructorCall cc) { + exists(RefType t | + implicitSetEnclosingInstance(cc, t) and + cc.getEnclosingCallable().getDeclaringType().getASourceSupertype*() = t + ) +} + +/** + * Gets the closest enclosing type of `ic` that is also a subtype of `t`. + */ +private RefType getEnclosing(InnerClass ic, RefType t) { + exists(RefType enclosing | enclosing = ic.getEnclosingType() | + if enclosing.getASourceSupertype*() = t then + result = enclosing + else + result = getEnclosing(enclosing, t) + ) +} + +/** + * Holds if `cc` implicitly sets the enclosing instance of type `t2` of the + * constructed inner class to `t1.this`. + */ +private predicate implicitEnclosingThisCopy(ConstructorCall cc, RefType t1, RefType t2) { + implicitSetEnclosingInstance(cc, t2) and + not implicitSetEnclosingInstanceToThis(cc) and + t1 = getEnclosing(cc.getEnclosingCallable().getDeclaringType(), t2) +} + +/** + * Holds if an enclosing instance of the form `t.this` is accessed by `e`. + */ +private predicate enclosingInstanceAccess(ExprParent e, RefType t) { + e.(InstanceAccess).isEnclosingInstanceAccess(t) or + exists(MethodAccess ma | ma.isEnclosingMethodAccess(t) and ma = e and not exists(ma.getQualifier())) or + exists(FieldAccess fa | fa.isEnclosingFieldAccess(t) and fa = e and not exists(fa.getQualifier())) or + implicitEnclosingThisCopy(e, t, _) +} + +/** + * Holds if an enclosing instance of the form `t2.this` is accessed by `e`, and + * this desugars into `this.enclosing.enclosing...enclosing`. The prefix of the + * desugared access with `i` enclosing instance field accesses has type `t1`. + */ +private predicate derivedInstanceAccess(ExprParent e, int i, RefType t1, RefType t2) { + enclosingInstanceAccess(e, t2) and + i = 0 and + exists(Callable c | c = e.(Expr).getEnclosingCallable() or c = e.(Stmt).getEnclosingCallable() | t1 = c.getDeclaringType()) + or + exists(InnerClass ic | + derivedInstanceAccess(e, i - 1, ic, t2) and + ic.getEnclosingType() = t1 and + ic != t2 + ) +} + +cached +private newtype TInstanceAccessExt = + TExplicitInstanceAccess(InstanceAccess ia) or + TThisQualifier(FieldAccess fa) { + fa.isOwnFieldAccess() and not exists(fa.getQualifier()) + } or + TThisArgument(Call c) { + c instanceof ThisConstructorInvocationStmt or + c instanceof SuperConstructorInvocationStmt or + c.(MethodAccess).isOwnMethodAccess() and not exists(c.getQualifier()) + } or + TThisEnclosingInstanceCapture(ConstructorCall cc) { + implicitSetEnclosingInstanceToThis(cc) + } or + TEnclosingInstanceAccess(ExprParent e, RefType t) { + enclosingInstanceAccess(e, t) and not e instanceof InstanceAccess + } or + TInstanceAccessQualifier(ExprParent e, int i, RefType t1, RefType t2) { + derivedInstanceAccess(e, i, t1, t2) and t1 != t2 + } + +/** + * A generalization of `InstanceAccess` that includes implicit accesses. + * + * The accesses can be divided into 6 kinds: + * - Explicit: Represented by an `InstanceAccess`. + * - Implicit field qualifier: The implicit access associated with an + * unqualified `FieldAccess` to a non-static field. + * - Implicit method qualifier: The implicit access associated with an + * unqualified `MethodAccess` to a non-static method. + * - Implicit this constructor argument: The implicit argument of the value of + * `this` to a constructor call of the form `this()` or `super()`. + * - Implicit enclosing instance capture: The implicit capture of the value of + * the directly enclosing instance of a constructed inner class. This is + * associated with an unqualified constructor call. + * - Implicit enclosing instance qualifier: The instance access that occurs as + * the implicit qualifier of a desugared enclosing instance access. + * + * Of these 6 kinds, the fourth (implicit this constructor argument) is always + * an `OwnInstanceAccess`, whereas the other 5 can be either `OwnInstanceAccess` + * or `EnclosingInstanceAccess`. + */ +class InstanceAccessExt extends TInstanceAccessExt { + private string ppBase() { + exists(EnclosingInstanceAccess enc | enc = this | + result = enc.getQualifier().toString() + "(" + enc.getType() + ")enclosing" + ) or + isOwnInstanceAccess() and result = "this" + } + + private string ppKind() { + isExplicit(_) and result = " <" + getAssociatedExprOrStmt().toString() + ">" or + isImplicitFieldQualifier(_) and result = " <.field>" or + isImplicitMethodQualifier(_) and result = " <.method>" or + isImplicitThisConstructorArgument(_) and result = " " or + isImplicitEnclosingInstanceCapture(_) and result = " <.new>" or + isImplicitEnclosingInstanceQualifier(_) and result = "." + } + + /** Gets a textual representation of this element. */ + string toString() { + result = ppBase() + ppKind() + } + + /** Gets the source location for this element. */ + Location getLocation() { result = getAssociatedExprOrStmt().getLocation() } + + private ExprParent getAssociatedExprOrStmt() { + this = TExplicitInstanceAccess(result) or + this = TThisQualifier(result) or + this = TThisArgument(result) or + this = TThisEnclosingInstanceCapture(result) or + this = TEnclosingInstanceAccess(result, _) or + this = TInstanceAccessQualifier(result, _, _, _) + } + + /** Gets the callable in which this instance access occurs. */ + Callable getEnclosingCallable() { + result = getAssociatedExprOrStmt().(Expr).getEnclosingCallable() or + result = getAssociatedExprOrStmt().(Stmt).getEnclosingCallable() + } + + /** Holds if this is the explicit instance access `ia`. */ + predicate isExplicit(InstanceAccess ia) { this = TExplicitInstanceAccess(ia) } + + /** Holds if this is the implicit qualifier of `fa`. */ + predicate isImplicitFieldQualifier(FieldAccess fa) { + this = TThisQualifier(fa) or + this = TEnclosingInstanceAccess(fa, _) + } + + /** Holds if this is the implicit qualifier of `ma`. */ + predicate isImplicitMethodQualifier(MethodAccess ma) { + this = TThisArgument(ma) or + this = TEnclosingInstanceAccess(ma, _) + } + + /** + * Holds if this is the implicit `this` argument of `cc`, which is either a + * `ThisConstructorInvocationStmt` or a `SuperConstructorInvocationStmt`. + */ + predicate isImplicitThisConstructorArgument(ConstructorCall cc) { + this = TThisArgument(cc) + } + + /** Holds if this is the implicit qualifier of `cc`.*/ + predicate isImplicitEnclosingInstanceCapture(ConstructorCall cc) { + this = TThisEnclosingInstanceCapture(cc) or + this = TEnclosingInstanceAccess(cc, _) + } + + /** + * Holds if this is the implicit qualifier of the desugared enclosing + * instance access `enc`. + */ + predicate isImplicitEnclosingInstanceQualifier(EnclosingInstanceAccess enc) { + enc.getQualifier() = this + } + + /** Holds if this is an access to an object's own instance. */ + predicate isOwnInstanceAccess() { + not isEnclosingInstanceAccess(_) + } + + /** Holds if this is an access to an enclosing instance. */ + predicate isEnclosingInstanceAccess(RefType t) { + exists(InstanceAccess ia | this = TExplicitInstanceAccess(ia) and ia.isEnclosingInstanceAccess(t)) or + this = TEnclosingInstanceAccess(_, t) or + exists(int i | this = TInstanceAccessQualifier(_, i, t, _) and i > 0) + } + + /** Gets the type of this instance access. */ + RefType getType() { + isEnclosingInstanceAccess(result) or + isOwnInstanceAccess() and result = getEnclosingCallable().getDeclaringType() + } + + /** Gets the control flow node associated with this instance access. */ + ControlFlowNode getCfgNode() { + exists(ExprParent e | e = getAssociatedExprOrStmt() | + e instanceof Call and result = e or + e instanceof InstanceAccess and result = e or + exists(FieldAccess fa | fa = e | + if fa instanceof RValue then fa = result + else result.(AssignExpr).getDest() = fa + ) + ) + } +} + +/** + * An access to an object's own instance. + */ +class OwnInstanceAccess extends InstanceAccessExt { + OwnInstanceAccess() { isOwnInstanceAccess() } +} + +/** + * An access to an enclosing instance. + */ +class EnclosingInstanceAccess extends InstanceAccessExt { + EnclosingInstanceAccess() { isEnclosingInstanceAccess(_) } + + /** Gets the implicit qualifier of this in the desugared representation. */ + InstanceAccessExt getQualifier() { + exists(ExprParent e, int i | + result = TInstanceAccessQualifier(e, i, _, _) + | + this = TInstanceAccessQualifier(e, i + 1, _, _) or + exists(RefType t | + derivedInstanceAccess(e, i + 1, t, t) + | + this = TEnclosingInstanceAccess(e, t) or + this = TExplicitInstanceAccess(e) + ) + ) + } +} diff --git a/java/ql/src/semmle/code/java/dataflow/IntegerGuards.qll b/java/ql/src/semmle/code/java/dataflow/IntegerGuards.qll new file mode 100644 index 00000000000..8bc7ba9a871 --- /dev/null +++ b/java/ql/src/semmle/code/java/dataflow/IntegerGuards.qll @@ -0,0 +1,101 @@ +/** + * Provides classes and predicates for integer guards. + */ + +import java +private import SSA +private import RangeUtils +private import RangeAnalysis + +/** Gets an expression that might have the value `i`. */ +private Expr exprWithIntValue(int i) { + result.(ConstantIntegerExpr).getIntValue() = i or + result.(ParExpr).getExpr() = exprWithIntValue(i) or + result.(ConditionalExpr).getTrueExpr() = exprWithIntValue(i) or + result.(ConditionalExpr).getFalseExpr() = exprWithIntValue(i) +} + +/** + * An expression for which the predicate `integerGuard` is relevant. + * This includes `RValue` and `MethodAccess`. + */ +class IntComparableExpr extends Expr { + IntComparableExpr() { + this instanceof RValue or this instanceof MethodAccess + } + + /** Gets an integer that is directly assigned to the expression in case of a variable; or zero. */ + int relevantInt() { + exists(SsaExplicitUpdate ssa, SsaSourceVariable v | + this = v.getAnAccess() and + ssa.getSourceVariable() = v and + ssa.getDefiningExpr().(VariableAssign).getSource() = exprWithIntValue(result) + ) or + result = 0 + } +} + +/** + * An expression that directly tests whether a given expression is equal to `k` or not. + * The set of `k`s is restricted to those that are relevant for the expression or + * have a direct comparison with the expression. + * + * If `result` evaluates to `branch`, then `e` is guaranteed to be equal to `k` if `is_k` + * is true, and different from `k` if `is_k` is false. + */ +pragma[nomagic] +Expr integerGuard(IntComparableExpr e, boolean branch, int k, boolean is_k) { + exists(EqualityTest eqtest, boolean polarity | + eqtest = result and + eqtest.hasOperands(e, any(ConstantIntegerExpr c | c.getIntValue() = k)) and + polarity = eqtest.polarity() and + (branch = true and is_k = polarity or branch = false and is_k = polarity.booleanNot()) + ) or + exists(EqualityTest eqtest, int val, Expr c, boolean upper | + k = e.relevantInt() and + eqtest = result and + eqtest.hasOperands(e, c) and + bounded(c, any(ZeroBound zb), val, upper, _) and + is_k = false and + (upper = true and val < k or upper = false and val > k) and + branch = eqtest.polarity() + ) or + exists(ComparisonExpr comp, Expr c, int val, boolean upper | + k = e.relevantInt() and + comp = result and + comp.hasOperands(e, c) and + bounded(c, any(ZeroBound zb), val, upper, _) and + is_k = false + | + comp.getLesserOperand() = c and comp.isStrict() and branch = true and val >= k and upper = false or // k <= val <= c < e, so e != k + comp.getLesserOperand() = c and comp.isStrict() and branch = false and val < k and upper = true or + comp.getLesserOperand() = c and not comp.isStrict() and branch = true and val > k and upper = false or + comp.getLesserOperand() = c and not comp.isStrict() and branch = false and val <= k and upper = true or + comp.getGreaterOperand() = c and comp.isStrict() and branch = true and val <= k and upper = true or + comp.getGreaterOperand() = c and comp.isStrict() and branch = false and val > k and upper = false or + comp.getGreaterOperand() = c and not comp.isStrict() and branch = true and val < k and upper = true or + comp.getGreaterOperand() = c and not comp.isStrict() and branch = false and val >= k and upper = false + ) +} + +/** + * A guard that splits the values of a variable into one range with an upper bound of `k-1` + * and one with a lower bound of `k`. + * + * If `branch_with_lower_bound_k` is true then `result` is equivalent to `k <= x` + * and if it is false then `result` is equivalent to `k > x`. + */ +Expr intBoundGuard(RValue x, boolean branch_with_lower_bound_k, int k) { + exists(ComparisonExpr comp, ConstantIntegerExpr c, int val | + comp = result and + comp.hasOperands(x, c) and + c.getIntValue() = val and + x.getVariable().getType() instanceof IntegralType + | + comp.getLesserOperand().getProperExpr() = c and comp.isStrict() and branch_with_lower_bound_k = true and val + 1 = k or // c < x + comp.getLesserOperand().getProperExpr() = c and not comp.isStrict() and branch_with_lower_bound_k = true and val = k or // c <= x + comp.getGreaterOperand().getProperExpr() = c and comp.isStrict() and branch_with_lower_bound_k = false and val = k or // x < c + comp.getGreaterOperand().getProperExpr() = c and not comp.isStrict() and branch_with_lower_bound_k = false and val + 1 = k // x <= c + ) +} + diff --git a/java/ql/src/semmle/code/java/dataflow/NullGuards.qll b/java/ql/src/semmle/code/java/dataflow/NullGuards.qll new file mode 100644 index 00000000000..92ed6d4b683 --- /dev/null +++ b/java/ql/src/semmle/code/java/dataflow/NullGuards.qll @@ -0,0 +1,217 @@ +/** + * Provides classes and predicates for null guards. + */ + +import java +import SSA +private import semmle.code.java.controlflow.internal.GuardsLogic +private import RangeUtils +private import IntegerGuards + +/** Gets an expression that is always `null`. */ +Expr alwaysNullExpr() { + result instanceof NullLiteral or + result.(ParExpr).getExpr() = alwaysNullExpr() or + result.(CastExpr).getExpr() = alwaysNullExpr() +} + +/** Gets an equality test between an expression `e` and an enum constant `c`. */ +Expr enumConstEquality(Expr e, boolean polarity, EnumConstant c) { + exists(EqualityTest eqtest | + eqtest = result and + eqtest.hasOperands(e, c.getAnAccess()) and + polarity = eqtest.polarity() + ) +} + +/** Gets an expression that is provably not `null`. */ +Expr clearlyNotNullExpr(Expr reason) { + result instanceof ClassInstanceExpr and reason = result or + result instanceof ArrayCreationExpr and reason = result or + result instanceof TypeLiteral and reason = result or + result instanceof ThisAccess and reason = result or + result instanceof StringLiteral and reason = result or + result instanceof AddExpr and result.getType() instanceof TypeString and reason = result or + exists(Field f | + result = f.getAnAccess() and + (f.hasName("TRUE") or f.hasName("FALSE")) and + f.getDeclaringType().hasQualifiedName("java.lang", "Boolean") and + reason = result + ) or + result.(ParExpr).getExpr() = clearlyNotNullExpr(reason) or + result.(CastExpr).getExpr() = clearlyNotNullExpr(reason) or + result.(AssignExpr).getSource() = clearlyNotNullExpr(reason) or + exists(ConditionalExpr c, Expr r1, Expr r2 | + c = result and + c.getTrueExpr() = clearlyNotNullExpr(r1) and + c.getFalseExpr() = clearlyNotNullExpr(r2) and + (reason = r1 or reason = r2) + ) or + exists(SsaVariable v, boolean branch, RValue rval, Guard guard | + guard = directNullGuard(v, branch, false) and + guard.controls(rval.getBasicBlock(), branch) and + reason = guard and + rval = v.getAUse() and + result = rval + ) or + exists(SsaVariable v | clearlyNotNull(v, reason) and result = v.getAUse()) +} + +/** Holds if `v` is an SSA variable that is provably not `null`. */ +predicate clearlyNotNull(SsaVariable v, Expr reason) { + exists(Expr src | + src = v.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() and + src = clearlyNotNullExpr(reason) + ) or + exists(CatchClause cc, LocalVariableDeclExpr decl | + decl = cc.getVariable() and + decl = v.(SsaExplicitUpdate).getDefiningExpr() and + reason = decl + ) or + exists(SsaVariable captured | + v.(SsaImplicitInit).captures(captured) and + clearlyNotNull(captured, reason) + ) +} + +/** Gets an expression that is provably not `null`. */ +Expr clearlyNotNullExpr() { + result = clearlyNotNullExpr(_) +} + +/** Holds if `v` is an SSA variable that is provably not `null`. */ +predicate clearlyNotNull(SsaVariable v) { + clearlyNotNull(v, _) +} + +/** + * Gets an expression that directly tests whether a given expression, `e`, is null or not. + * + * If `result` evaluates to `branch`, then `e` is guaranteed to be null if `isnull` + * is true, and non-null if `isnull` is false. + */ +Expr basicNullGuard(Expr e, boolean branch, boolean isnull) { + exists(EqualityTest eqtest, boolean polarity | + eqtest = result and + eqtest.hasOperands(e, any(NullLiteral n)) and + polarity = eqtest.polarity() and + (branch = true and isnull = polarity or branch = false and isnull = polarity.booleanNot()) + ) or + result.(InstanceOfExpr).getExpr() = e and branch = true and isnull = false or + exists(MethodAccess call, Method m, boolean polarity | + call = result and + call.getAnArgument() = e and + call.getMethod() = m and + m.getDeclaringType().hasQualifiedName("java.util", "Objects") and + (m.hasName("isNull") and polarity = true or m.hasName("nonNull") and polarity = false) and + (branch = true and isnull = polarity or branch = false and isnull = polarity.booleanNot()) + ) or + exists(MethodAccess call | + call = result and + call.getAnArgument() = e and + call.getMethod() instanceof EqualsMethod and + branch = true and + isnull = false + ) or + exists(EqualityTest eqtest | + eqtest = result and + eqtest.hasOperands(e, clearlyNotNullExpr()) and + isnull = false and + branch = eqtest.polarity() + ) or + result = enumConstEquality(e, branch, _) and isnull = false +} + +/** + * Gets an expression that directly tests whether a given expression, `e`, is null or not. + * + * If `result` evaluates to `branch`, then `e` is guaranteed to be null if `isnull` + * is true, and non-null if `isnull` is false. + */ +Expr basicOrCustomNullGuard(Expr e, boolean branch, boolean isnull) { + result = basicNullGuard(e, branch, isnull) or + exists(MethodAccess call, Method m, int ix | + call = result and + call.getArgument(ix) = e and + call.getMethod().getSourceDeclaration() = m and + m = customNullGuard(ix, branch, isnull) + ) +} + +/** + * Gets an expression that directly tests whether a given SSA variable is null or not. + * + * If `result` evaluates to `branch`, then `v` is guaranteed to be null if `isnull` + * is true, and non-null if `isnull` is false. + */ +Expr directNullGuard(SsaVariable v, boolean branch, boolean isnull) { + result = basicOrCustomNullGuard(sameValue(v, _), branch, isnull) +} + +/** + * Gets a `Guard` that tests (possibly indirectly) whether a given SSA variable is null or not. + * + * If `result` evaluates to `branch`, then `v` is guaranteed to be null if `isnull` + * is true, and non-null if `isnull` is false. + */ +Guard nullGuard(SsaVariable v, boolean branch, boolean isnull) { + result = directNullGuard(v, branch, isnull) or + exists(boolean branch0 | implies_v3(result, branch, nullGuard(v, branch0, isnull), branch0)) +} + +/** + * A return statement that on a return value of `retval` allows the conclusion that the + * parameter `p` either is null or non-null as specified by `isnull`. + */ +private predicate validReturnInCustomNullGuard(ReturnStmt ret, Parameter p, boolean retval, boolean isnull) { + exists(Method m | + ret.getEnclosingCallable() = m and + p.getCallable() = m and + m.getReturnType().(PrimitiveType).hasName("boolean") + ) and + exists(SsaImplicitInit ssa | ssa.isParameterDefinition(p) | + nullGuardedReturn(ret, ssa, isnull) and + (retval = true or retval = false) + or + exists(Expr res | res = ret.getResult() | + res = nullGuard(ssa, retval, isnull) + ) + ) +} + +private predicate nullGuardedReturn(ReturnStmt ret, SsaImplicitInit ssa, boolean isnull) { + exists(boolean branch | + nullGuard(ssa, branch, isnull).directlyControls(ret.getBasicBlock(), branch) + ) +} + +/** + * Gets a non-overridable method with a boolean return value that performs a null-check + * on the `index`th parameter. A return value equal to `retval` allows us to conclude + * that the argument either is null or non-null as specified by `isnull`. + */ +private Method customNullGuard(int index, boolean retval, boolean isnull) { + exists(Parameter p | + result.getReturnType().(PrimitiveType).hasName("boolean") and + not result.isOverridable() and + p.getCallable() = result and + not p.isVarargs() and + p.getType() instanceof RefType and + p.getPosition() = index and + forex(ReturnStmt ret | + ret.getEnclosingCallable() = result and + exists(Expr res | res = ret.getResult() | not res.(BooleanLiteral).getBooleanValue() = retval.booleanNot()) + | + validReturnInCustomNullGuard(ret, p, retval, isnull) + ) + ) +} + +/** + * `guard` is a guard expression that suggests that `v` might be null. + * + * This is equivalent to `guard = basicNullGuard(sameValue(v, _), _, true)`. + */ +predicate guardSuggestsVarMaybeNull(Expr guard, SsaVariable v) { + guard = basicNullGuard(sameValue(v, _), _, true) +} diff --git a/java/ql/src/semmle/code/java/dataflow/Nullness.qll b/java/ql/src/semmle/code/java/dataflow/Nullness.qll new file mode 100644 index 00000000000..4932af01eb5 --- /dev/null +++ b/java/ql/src/semmle/code/java/dataflow/Nullness.qll @@ -0,0 +1,673 @@ +/** + * Provides classes and predicates for nullness analysis. + * + * Local variables that may be null are tracked to see if they might reach + * a dereference and cause a NullPointerException. Assertions are assumed to + * hold, so results guarded by, for example, `assert x != null;` or + * `if (x == null) { assert false; }` are excluded. + */ + +/* + * Implementation details: + * + * The three exported predicates, `nullDeref`, `alwaysNullDeref`, and + * `superfluousNullGuard`, compute potential null dereferences, definite null + * dereferences, and superfluous null checks, respectively. The bulk of the + * library supports `nullDeref`, while the latter two are fairly simple in + * comparison. + * + * The NPE (NullPointerException) candidates are computed by + * `nullDerefCandidate` and consist of three parts: A variable definition that + * might be null as computed by `varMaybeNull`, a dereference that can cause a + * NPE as computed by `firstVarDereferenceInBlock`, and a control flow path + * between the two points. The path is computed by `varMaybeNullInBlock`, + * which is the transitive closure of the step relation `nullVarStep` + * originating in a definition given by `varMaybeNull`. The step relation + * `nullVarStep` is essentially just the successor relation on basic blocks + * restricted to exclude edges along which the variable cannot be null. + * + * The step relation `nullVarStep` is then reused twice to produce two + * refinements of the path reachability predicate `varMaybeNullInBlock` in + * order to prune impossible paths that would otherwise lead to a potential + * NPE. These two refinements are `varMaybeNullInBlock_corrCond` and + * `varMaybeNullInBlock_trackVar` and are described in further detail below. + */ + +import java +private import SSA +private import semmle.code.java.controlflow.Guards +private import RangeUtils +private import IntegerGuards +private import NullGuards +private import semmle.code.java.Collections +private import semmle.code.java.frameworks.Assertions + +/** Gets an expression that may be `null`. */ +Expr nullExpr() { + result instanceof NullLiteral or + result.(ParExpr).getExpr() = nullExpr() or + result.(ConditionalExpr).getTrueExpr() = nullExpr() or + result.(ConditionalExpr).getFalseExpr() = nullExpr() or + result.(AssignExpr).getSource() = nullExpr() or + result.(CastExpr).getExpr() = nullExpr() +} + +/** An expression of a boxed type that is implicitly unboxed. */ +private predicate unboxed(Expr e) { + e.getType() instanceof BoxedType and + ( + exists(ArrayAccess aa | aa.getIndexExpr() = e) or + exists(ArrayCreationExpr ace | ace.getADimension() = e) or + exists(LocalVariableDeclExpr decl | decl.getVariable().getType() instanceof PrimitiveType and decl.getInit() = e) or + exists(AssignExpr assign | assign.getDest().getType() instanceof PrimitiveType and assign.getSource() = e) or + exists(AssignOp assign | assign.getSource() = e and assign.getType() instanceof PrimitiveType) or + exists(EqualityTest eq | eq.getAnOperand() = e and eq.getAnOperand().getType() instanceof PrimitiveType) or + exists(BinaryExpr bin | bin.getAnOperand() = e and not bin instanceof EqualityTest and bin.getType() instanceof PrimitiveType) or + exists(UnaryExpr un | un.getExpr() = e) or + exists(ConditionalExpr cond | cond.getType() instanceof PrimitiveType | cond.getTrueExpr() = e or cond.getFalseExpr() = e) or + exists(ConditionNode cond | cond.getCondition() = e) or + exists(Parameter p | p.getType() instanceof PrimitiveType and p.getAnArgument() = e) or + exists(ReturnStmt ret | ret.getEnclosingCallable().getReturnType() instanceof PrimitiveType and ret.getResult() = e) + ) +} + +/** An expression that is being dereferenced. These are the points where `NullPointerException`s can occur. */ +predicate dereference(Expr e) { + exists(EnhancedForStmt for | for.getExpr() = e) or + exists(SynchronizedStmt synch | synch.getExpr() = e) or + exists(SwitchStmt switch | switch.getExpr() = e) or + exists(FieldAccess fa, Field f | fa.getQualifier() = e and fa.getField() = f and not f.isStatic()) or + exists(MethodAccess ma, Method m | ma.getQualifier() = e and ma.getMethod() = m and not m.isStatic()) or + exists(ClassInstanceExpr cie | cie.getQualifier() = e) or + exists(ArrayAccess aa | aa.getArray() = e) or + exists(CastExpr cast | cast.getExpr() = e and e.getType() instanceof BoxedType and cast.getType() instanceof PrimitiveType) or + unboxed(e) +} + +/** + * Gets the `ControlFlowNode` in which the given SSA variable is being dereferenced. + * + * The `VarAccess` is included for nicer error reporting. + */ +private ControlFlowNode varDereference(SsaVariable v, VarAccess va) { + exists(Expr e | + dereference(e) and + e = sameValue(v, va) and + result = e.getProperExpr() + ) +} + +/** + * A `ControlFlowNode` that ensures that the SSA variable is not null in any + * subsequent use, either by dereferencing it or by an assertion. + */ +private ControlFlowNode ensureNotNull(SsaVariable v) { + result = varDereference(v, _) or + result.(AssertStmt).getExpr() = nullGuard(v, true, false) or + exists(AssertTrueMethod m | result = m.getACheck(nullGuard(v, true, false))) or + exists(AssertFalseMethod m | result = m.getACheck(nullGuard(v, false, false))) or + exists(AssertNotNullMethod m | result = m.getACheck(v.getAUse())) +} + +/** + * A variable dereference that cannot be reached by a `null` value, because of an earlier + * dereference or assertion in the same `BasicBlock`. + */ +private predicate unreachableVarDereference(BasicBlock bb, SsaVariable v, ControlFlowNode varDeref) { + exists(ControlFlowNode n, int i, int j | + (n = ensureNotNull(v) or assertFail(bb, n)) and + varDeref = varDereference(v, _) and + bb.getNode(i) = n and + bb.getNode(j) = varDeref and + i < j + ) +} + +/** + * The first dereference of a variable in a given `BasicBlock` excluding those dereferences + * that are preceded by a not-null assertion or a trivially failing assertion. + */ +private predicate firstVarDereferenceInBlock(BasicBlock bb, SsaVariable v, VarAccess va) { + exists(ControlFlowNode n | + varDereference(v, va) = n and + n.getBasicBlock() = bb and + not unreachableVarDereference(bb, v, n) + ) +} + +/** A variable suspected of being `null`. */ +private predicate varMaybeNull(SsaVariable v, string msg, Expr reason) { + // A variable compared to null might be null. + exists(Expr e | + reason = e and + msg = "as suggested by $@ null guard" and + guardSuggestsVarMaybeNull(e, v) and + not v instanceof SsaPhiNode and + not clearlyNotNull(v) and + // Comparisons in finally blocks are excluded since missing exception edges in the CFG could otherwise yield FPs. + not exists(TryStmt try | + try.getFinally() = e.getEnclosingStmt().getParent*() + ) and + ( + exists(ConditionalExpr c | c.getCondition().getAChildExpr*() = e) or + not exists(MethodAccess ma | + ma.getAnArgument().getAChildExpr*() = e + ) + ) and + // Don't use a guard as reason if there is a null assignment. + not v.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() = nullExpr() + ) or + // A parameter might be null if there is a null argument somewhere. + exists(Parameter p, Expr arg | + v.(SsaImplicitInit).isParameterDefinition(p) and + p.getAnArgument() = arg and + reason = arg and + msg = "because of $@ null argument" and + arg = nullExpr() and + not arg.getEnclosingCallable().getEnclosingCallable*() instanceof TestMethod + ) or + // If the source of a variable is null then the variable may be null. + exists(VariableAssign def | + v.(SsaExplicitUpdate).getDefiningExpr() = def and + def.getSource() = nullExpr() and + reason = def and + msg = "because of $@ assignment" + ) +} + +/** Gets an array or collection that contains at least one element. */ +private Expr nonEmptyExpr() { + // An array creation with a known positive size is trivially non-empty. + result.(ArrayCreationExpr).getFirstDimensionSize() > 0 or + exists(SsaVariable v | + // A use of an array variable is non-empty if... + result = v.getAUse() and + v.getSourceVariable().getType() instanceof Array + | + // ...its definition is non-empty... + v.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() = nonEmptyExpr() or + // ...or it is guarded by a condition proving its length to be non-zero. + exists(ConditionBlock cond, boolean branch, FieldAccess length | + cond.controls(result.getBasicBlock(), branch) and + cond.getCondition() = integerGuard(length, branch, 0, false) and + length.getField().hasName("length") and + length.getQualifier() = v.getAUse() + ) + ) or + exists(SsaVariable v | + // A use of a Collection variable is non-empty if... + result = v.getAUse() and + v.getSourceVariable().getType() instanceof CollectionType and + exists(ConditionBlock cond, boolean branch, Expr c | + // ...it is guarded by a condition... + cond.controls(result.getBasicBlock(), branch) and + // ...and it isn't modified in the scope of the condition... + forall(MethodAccess ma, Method m | + m = ma.getMethod() and + ma.getQualifier() = v.getSourceVariable().getAnAccess() and + cond.controls(ma.getBasicBlock(), branch) + | + m instanceof CollectionQueryMethod + ) and + cond.getCondition() = c + | + // ...and the condition proves that it is non-empty, either by using the `isEmpty` method... + c.(MethodAccess).getMethod().hasName("isEmpty") and branch = false and c.(MethodAccess).getQualifier() = v.getAUse() or + // ...or a check on its `size`. + exists(MethodAccess size | + c = integerGuard(size, branch, 0, false) and + size.getMethod().hasName("size") and + size.getQualifier() = v.getAUse() + ) + ) + ) +} + +/** The control flow edge that exits an enhanced for loop if the `Iterable` is empty. */ +private predicate enhancedForEarlyExit(EnhancedForStmt for, ControlFlowNode n1, ControlFlowNode n2) { + exists(Expr forExpr | + n1.getANormalSuccessor() = n2 and + for.getExpr() = forExpr and + forExpr.getAChildExpr*() = n1 and + not forExpr.getAChildExpr*() = n2 and + n1.getANormalSuccessor() = for.getVariable() and + not n2 = for.getVariable() + ) +} + +/** A control flow edge that cannot be taken. */ +private predicate impossibleEdge(BasicBlock bb1, BasicBlock bb2) { + exists(EnhancedForStmt for | + enhancedForEarlyExit(for, bb1.getANode(), bb2.getANode()) and + for.getExpr() = nonEmptyExpr() + ) +} + +/** A control flow edge that leaves a finally-block. */ +private predicate leavingFinally(BasicBlock bb1, BasicBlock bb2, boolean normaledge) { + exists(TryStmt try, Block finally | + try.getFinally() = finally and + bb1.getABBSuccessor() = bb2 and + bb1.getEnclosingStmt().getParent*() = finally and + not bb2.getEnclosingStmt().getParent*() = finally and + if bb1.getLastNode().getANormalSuccessor() = bb2.getFirstNode() then normaledge = true else normaledge = false + ) +} + +private predicate ssaSourceVarMaybeNull(SsaSourceVariable v) { + varMaybeNull(v.getAnSsaVariable(), _, _) +} + +/** + * The step relation for propagating that a given SSA variable might be `null` in a given `BasicBlock`. + * + * If `midssa` is null in `mid` then `ssa` might be null in `bb`. The SSA variables share the same + * `SsaSourceVariable`. + * + * A boolean flag tracks whether a non-normal completion is waiting to resume upon the exit of a finally-block. + * If the flag is set, then the normal edge out of the finally-block is prohibited, but if it is not set then + * no knowledge is assumed of any potentially waiting completions. `midstoredcompletion` is the flag before + * the step and `storedcompletion` is the flag after the step. + */ +private predicate nullVarStep(SsaVariable midssa, BasicBlock mid, boolean midstoredcompletion, SsaVariable ssa, BasicBlock bb, boolean storedcompletion) { + exists(SsaSourceVariable v | + ssaSourceVarMaybeNull(v) and + midssa.getSourceVariable() = v + | + ssa.(SsaPhiNode).getAPhiInput() = midssa and ssa.getBasicBlock() = bb or + ssa = midssa and not exists(SsaPhiNode phi | phi.getSourceVariable() = v and phi.getBasicBlock() = bb) + ) and + (midstoredcompletion = true or midstoredcompletion = false) and + midssa.isLiveAtEndOfBlock(mid) and + not ensureNotNull(midssa).getBasicBlock() = mid and + not assertFail(mid, _) and + bb = mid.getABBSuccessor() and + not impossibleEdge(mid, bb) and + not exists(boolean branch | + nullGuard(midssa, branch, false).hasBranchEdge(mid, bb, branch) + ) and + not (leavingFinally(mid, bb, true) and midstoredcompletion = true) and + if bb.getFirstNode() = any(TryStmt try | | try.getFinally()) then + (if bb.getFirstNode() = mid.getLastNode().getANormalSuccessor() then storedcompletion = false else storedcompletion = true) + else if leavingFinally(mid, bb, _) then + storedcompletion = false + else + storedcompletion = midstoredcompletion +} + +/** + * The transitive closure of `nullVarStep` originating from `varMaybeNull`. That is, those `BasicBlock`s + * for which the SSA variable is suspected of being `null`. + */ +private predicate varMaybeNullInBlock(SsaVariable ssa, SsaSourceVariable v, BasicBlock bb, boolean storedcompletion) { + varMaybeNull(ssa, _, _) and bb = ssa.getBasicBlock() and storedcompletion = false and v = ssa.getSourceVariable() or + exists(BasicBlock mid, SsaVariable midssa, boolean midstoredcompletion | + varMaybeNullInBlock(midssa, v, mid, midstoredcompletion) and + nullVarStep(midssa, mid, midstoredcompletion, ssa, bb, storedcompletion) + ) +} + +/** + * Holds if `v` is a source variable that might reach a potential `null` + * dereference. + */ +private predicate nullDerefCandidateVariable(SsaSourceVariable v) { + exists(SsaVariable ssa, BasicBlock bb | + firstVarDereferenceInBlock(bb, ssa, _) and + varMaybeNullInBlock(ssa, v, bb, _) + ) +} + +private predicate varMaybeNullInBlock_origin(SsaVariable origin, SsaVariable ssa, BasicBlock bb, boolean storedcompletion) { + nullDerefCandidateVariable(ssa.getSourceVariable()) and + varMaybeNull(ssa, _, _) and bb = ssa.getBasicBlock() and storedcompletion = false and origin = ssa + or + exists(BasicBlock mid, SsaVariable midssa, boolean midstoredcompletion | + varMaybeNullInBlock_origin(origin, midssa, mid, midstoredcompletion) and + nullVarStep(midssa, mid, midstoredcompletion, ssa, bb, storedcompletion) + ) +} + +/** + * A potential `null` dereference. That is, the first dereference of a variable in a block + * where it is suspected of being `null`. + */ +private predicate nullDerefCandidate(SsaVariable origin, VarAccess va) { + exists(SsaVariable ssa, BasicBlock bb | + firstVarDereferenceInBlock(bb, ssa, va) and + varMaybeNullInBlock_origin(origin, ssa, bb, _) + ) +} + +/* + * In the following, the potential `null` dereference candidates are pruned by proving that + * a `NullPointerException` (NPE) cannot occur. This is done by pruning the control-flow paths + * that lead to the NPE candidate in two ways: + * + * 1. For each set of correlated conditions that are passed by the path, consistent + * branches must be taken. For example, the following code is safe due to the two tests on + * `flag` begin correlated. + * ``` + * x = null; + * if (flag) x = new A(); + * if (flag) x.m(); + * ``` + * + * 2. For each other variable that changes its value alongside the potential NPE candidate, + * the passed conditions must be consistent with its value. For example, the following + * code is safe due to the value of `t`. + * ``` + * x = null; + * t = null; + * if (...) { x = new A(); t = new B(); } + * if (t != null) x.m(); + * ``` + * We call such a variable a _tracking variable_ as it tracks the null-ness of `x`. + */ + +/** A variable that is assigned `null` if the given condition takes the given branch. */ +private predicate varConditionallyNull(SsaExplicitUpdate v, ConditionBlock cond, boolean branch) { + exists(ConditionalExpr condexpr | + v.getDefiningExpr().(VariableAssign).getSource().getProperExpr() = condexpr and + condexpr.getCondition().getProperExpr() = cond.getCondition() + | + condexpr.getTrueExpr() = nullExpr() and branch = true and not condexpr.getFalseExpr() = nullExpr() or + condexpr.getFalseExpr() = nullExpr() and branch = false and not condexpr.getTrueExpr() = nullExpr() + ) + or + v.getDefiningExpr().(VariableAssign).getSource() = nullExpr() and + cond.controls(v.getBasicBlock(), branch) +} + +/** + * A condition that might be useful in proving an NPE candidate safe. + * + * This is a condition along the path that found the NPE candidate. + */ +private predicate interestingCond(SsaSourceVariable npecand, ConditionBlock cond) { + nullDerefCandidateVariable(npecand) and + (varMaybeNullInBlock(_, npecand, cond, _) or varConditionallyNull(npecand.getAnSsaVariable(), cond, _)) and + not cond.getCondition().getAChildExpr*() = npecand.getAnAccess() +} + +/** A pair of correlated conditions for a given NPE candidate. */ +private predicate correlatedConditions(SsaSourceVariable npecand, ConditionBlock cond1, ConditionBlock cond2, boolean inverted) { + interestingCond(npecand, cond1) and + interestingCond(npecand, cond2) and + cond1 != cond2 and + ( + exists(SsaVariable v | + cond1.getCondition() = v.getAUse() and + cond2.getCondition() = v.getAUse() and + inverted = false + ) or + exists(SsaVariable v, boolean branch1, boolean branch2 | + cond1.getCondition() = nullGuard(v, branch1, true) and + cond1.getCondition() = nullGuard(v, branch1.booleanNot(), false) and + cond2.getCondition() = nullGuard(v, branch2, true) and + cond2.getCondition() = nullGuard(v, branch2.booleanNot(), false) and + inverted = branch1.booleanXor(branch2) + ) or + exists(SsaVariable v, RValue rv1, RValue rv2, int k, boolean branch1, boolean branch2 | + rv1 = v.getAUse() and + rv2 = v.getAUse() and + cond1.getCondition() = integerGuard(rv1, branch1, k, true) and + cond1.getCondition() = integerGuard(rv1, branch1.booleanNot(), k, false) and + cond2.getCondition() = integerGuard(rv2, branch2, k, true) and + cond2.getCondition() = integerGuard(rv2, branch2.booleanNot(), k, false) and + inverted = branch1.booleanXor(branch2) + ) or + exists(SsaVariable v, int k, boolean branch1, boolean branch2 | + cond1.getCondition() = intBoundGuard(v.getAUse(), branch1, k) and + cond2.getCondition() = intBoundGuard(v.getAUse(), branch2, k) and + inverted = branch1.booleanXor(branch2) + ) or + exists(SsaVariable v, EnumConstant c, boolean pol1, boolean pol2 | + cond1.getCondition() = enumConstEquality(v.getAUse(), pol1, c) and + cond2.getCondition() = enumConstEquality(v.getAUse(), pol2, c) and + inverted = pol1.booleanXor(pol2) + ) + ) +} + +/** + * This is again the transitive closure of `nullVarStep` similarly to `varMaybeNullInBlock`, but + * this time restricted based on pairs of correlated conditions consistent with `cond1` + * evaluating to `branch`. + */ +private predicate varMaybeNullInBlock_corrCond(SsaVariable origin, SsaVariable ssa, BasicBlock bb, boolean storedcompletion, ConditionBlock cond1, boolean branch) { + exists(SsaSourceVariable npecand | npecand = ssa.getSourceVariable() | + nullDerefCandidateVariable(npecand) and correlatedConditions(npecand, cond1, _, _) + ) and + ( + varConditionallyNull(ssa, cond1, branch) or + not varConditionallyNull(ssa, cond1, _) and (branch = true or branch = false) + ) and + varMaybeNull(ssa, _, _) and bb = ssa.getBasicBlock() and storedcompletion = false and origin = ssa + or + exists(BasicBlock mid, SsaVariable midssa, boolean midstoredcompletion | + varMaybeNullInBlock_corrCond(origin, midssa, mid, midstoredcompletion, cond1, branch) and + ( + cond1 = mid and cond1.getTestSuccessor(branch) = bb or + exists(ConditionBlock cond2, boolean inverted, boolean branch2 | + cond2 = mid and + correlatedConditions(_, cond1, cond2, inverted) and + cond2.getTestSuccessor(branch2) = bb and + branch = branch2.booleanXor(inverted) + ) or + cond1 != mid and not exists(ConditionBlock cond2 | cond2 = mid and correlatedConditions(_, cond1, cond2, _)) + ) and + nullVarStep(midssa, mid, midstoredcompletion, ssa, bb, storedcompletion) + ) +} + +/* + * A tracking variable has its possible values divided into two sets, A and B, for + * which we can attribute at least one direct assignment to be contained in either + * A or B. + * Four kinds are supported: + * - null: A means null and B means non-null. + * - boolean: A means true and B means false. + * - enum: A means a specific enum constant and B means any other value. + * - int: A means a specific integer value and B means any other value. + */ + +newtype TrackVarKind = + TrackVarKindNull() or + TrackVarKindBool() or + TrackVarKindEnum() or + TrackVarKindInt() + +/** A variable that might be relevant as a tracking variable for the NPE candidate. */ +private predicate trackingVar(SsaSourceVariable npecand, SsaExplicitUpdate trackssa, SsaSourceVariable trackvar, TrackVarKind kind, Expr init) { + exists(ConditionBlock cond | + interestingCond(npecand, cond) and + varMaybeNullInBlock(_, npecand, cond, _) and + cond.getCondition().getAChildExpr*() = trackvar.getAnAccess() and + trackssa.getSourceVariable() = trackvar and + trackssa.getDefiningExpr().(VariableAssign).getSource() = init + | + init instanceof NullLiteral and kind = TrackVarKindNull() or + init = clearlyNotNullExpr() and kind = TrackVarKindNull() or + init instanceof BooleanLiteral and kind = TrackVarKindBool() or + init.(VarAccess).getVariable() instanceof EnumConstant and kind = TrackVarKindEnum() or + exists(init.(ConstantIntegerExpr).getIntValue()) and kind = TrackVarKindInt() + ) +} + +/** Gets an expression that tests the value of a given tracking variable. */ +private Expr trackingVarGuard(SsaVariable trackssa, SsaSourceVariable trackvar, TrackVarKind kind, boolean branch, boolean isA) { + exists(Expr init | trackingVar(_, trackssa, trackvar, kind, init) | + result = basicOrCustomNullGuard(trackvar.getAnAccess(), branch, isA) and kind = TrackVarKindNull() or + result = trackvar.getAnAccess() and kind = TrackVarKindBool() and (branch = true or branch = false) and isA = branch or + exists(boolean polarity, EnumConstant c, EnumConstant initc | + initc.getAnAccess() = init and + kind = TrackVarKindEnum() and + result = enumConstEquality(trackvar.getAnAccess(), polarity, c) and + ( + initc = c and branch = polarity.booleanNot() and isA = false or + initc = c and branch = polarity and isA = true or + initc != c and branch = polarity and isA = false + ) + ) or + exists(int k | + init.(ConstantIntegerExpr).getIntValue() = k and + kind = TrackVarKindInt() + | + result = integerGuard(trackvar.getAnAccess(), branch, k, isA) or + exists(int k2 | + result = integerGuard(trackvar.getAnAccess(), branch.booleanNot(), k2, true) and + isA = false and + k2 != k + ) or + exists(int bound, boolean branch_with_lower_bound | + result = intBoundGuard(trackvar.getAnAccess(), branch_with_lower_bound, bound) and + isA = false + | + branch = branch_with_lower_bound and k < bound or + branch = branch_with_lower_bound.booleanNot() and bound <= k + ) + ) + ) or + exists(EqualityTest eqtest, boolean branch0, boolean polarity, BooleanLiteral boollit | + eqtest = result and + eqtest.hasOperands(trackingVarGuard(trackssa, trackvar, kind, branch0, isA), boollit) and + eqtest.polarity() = polarity and + branch = branch0.booleanXor(polarity).booleanXor(boollit.getBooleanValue()) + ) +} + +/** An update to a tracking variable that is contained fully in either A or B. */ +private predicate isReset(SsaVariable trackssa, SsaSourceVariable trackvar, TrackVarKind kind, SsaExplicitUpdate update, boolean isA) { + exists(Expr init, Expr e | + trackingVar(_, trackssa, trackvar, kind, init) and + update.getSourceVariable() = trackvar and + e = update.getDefiningExpr().(VariableAssign).getSource() + | + e instanceof NullLiteral and kind = TrackVarKindNull() and isA = true or + e = clearlyNotNullExpr() and kind = TrackVarKindNull() and isA = false or + e.(BooleanLiteral).getBooleanValue() = isA and kind = TrackVarKindBool() or + e.(VarAccess).getVariable().(EnumConstant) = init.(VarAccess).getVariable() and kind = TrackVarKindEnum() and isA = true or + e.(VarAccess).getVariable().(EnumConstant) != init.(VarAccess).getVariable() and kind = TrackVarKindEnum() and isA = false or + e.(ConstantIntegerExpr).getIntValue() = init.(ConstantIntegerExpr).getIntValue() and kind = TrackVarKindInt() and isA = true or + e.(ConstantIntegerExpr).getIntValue() != init.(ConstantIntegerExpr).getIntValue() and kind = TrackVarKindInt() and isA = false + ) +} + +/** The abstract value of the tracked variable. */ +newtype TrackedValue = + TrackedValueA() or + TrackedValueB() or + TrackedValueUnknown() + +private TrackedValue trackValAorB(boolean isA) { isA = true and result = TrackedValueA() or isA = false and result = TrackedValueB() } + +/** A control flow edge passing through a condition that implies a specific value for a tracking variable. */ +private predicate stepImplies(BasicBlock bb1, BasicBlock bb2, SsaVariable trackssa, SsaSourceVariable trackvar, TrackVarKind kind, boolean isA) { + exists(ConditionBlock cond, boolean branch | + cond = bb1 and + cond.getTestSuccessor(branch) = bb2 and + cond.getCondition() = trackingVarGuard(trackssa, trackvar, kind, branch, isA) + ) +} + +/** + * This is again the transitive closure of `nullVarStep` similarly to `varMaybeNullInBlock`, but + * this time restricted based on a tracking variable. + */ +private predicate varMaybeNullInBlock_trackVar(SsaVariable origin, SsaVariable ssa, BasicBlock bb, boolean storedcompletion, SsaVariable trackssa, SsaSourceVariable trackvar, TrackVarKind kind, TrackedValue trackvalue) { + exists(SsaSourceVariable npecand | npecand = ssa.getSourceVariable() | + nullDerefCandidateVariable(npecand) and trackingVar(npecand, trackssa, trackvar, kind, _) + ) and + ( + exists(SsaVariable init, boolean isA | + init.getSourceVariable() = trackvar and + init.isLiveAtEndOfBlock(bb) and + isReset(trackssa, trackvar, kind, init, isA) and trackvalue = trackValAorB(isA) + ) or + trackvalue = TrackedValueUnknown() and + not exists(SsaVariable init | + init.getSourceVariable() = trackvar and + init.isLiveAtEndOfBlock(bb) and + isReset(trackssa, trackvar, kind, init, _) + ) + ) and + varMaybeNull(ssa, _, _) and bb = ssa.getBasicBlock() and storedcompletion = false and origin = ssa + or + exists(BasicBlock mid, SsaVariable midssa, boolean midstoredcompletion, TrackedValue trackvalue0 | + varMaybeNullInBlock_trackVar(origin, midssa, mid, midstoredcompletion, trackssa, trackvar, kind, trackvalue0) and + nullVarStep(midssa, mid, midstoredcompletion, ssa, bb, storedcompletion) and + ( + trackvalue0 = TrackedValueUnknown() or + // A step that implies a value that contradicts the current value is not allowed. + exists(boolean isA | trackvalue0 = trackValAorB(isA) | + not stepImplies(mid, bb, trackssa, trackvar, kind, isA.booleanNot()) + ) + ) and + ( + // If no update occurs then the tracked value is unchanged unless the step implies a given value via a condition. + not exists(SsaUpdate update | + update.getSourceVariable() = trackvar and + update.getBasicBlock() = bb + ) and + ( + exists(boolean isA | stepImplies(mid, bb, trackssa, trackvar, kind, isA) | trackvalue = trackValAorB(isA)) or + not stepImplies(mid, bb, trackssa, trackvar, kind, _) and trackvalue = trackvalue0 + ) + or + // If an update occurs then the tracked value is set accordingly. + exists(SsaUpdate update | + update.getSourceVariable() = trackvar and + update.getBasicBlock() = bb + | + exists(boolean isA | isReset(trackssa, trackvar, kind, update, isA) | trackvalue = trackValAorB(isA)) or + not isReset(trackssa, trackvar, kind, update, _) and trackvalue = TrackedValueUnknown() + ) + ) + ) +} + +/** + * A potential `null` dereference that has not been proven safe. + */ +predicate nullDeref(SsaSourceVariable v, VarAccess va, string msg, Expr reason) { + exists(SsaVariable origin, SsaVariable ssa, BasicBlock bb | + nullDerefCandidate(origin, va) and + varMaybeNull(origin, msg, reason) and + ssa.getSourceVariable() = v and + firstVarDereferenceInBlock(bb, ssa, va) and + forall(ConditionBlock cond | + correlatedConditions(v, cond, _, _) + | + varMaybeNullInBlock_corrCond(origin, ssa, bb, _, cond, _) + ) and + forall(SsaVariable guardssa, SsaSourceVariable guardvar, TrackVarKind kind | + trackingVar(v, guardssa, guardvar, kind, _) + | + varMaybeNullInBlock_trackVar(origin, ssa, bb, _, guardssa, guardvar, kind, _) + ) + ) +} + +/** + * A dereference of a variable that is always `null`. + */ +predicate alwaysNullDeref(SsaSourceVariable v, VarAccess va) { + exists(BasicBlock bb, SsaVariable ssa | + forall(SsaVariable def | def = ssa.getAnUltimateDefinition() | + def.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() = alwaysNullExpr() + ) or + exists(boolean branch | + nullGuard(ssa, branch, true).directlyControls(bb, branch) and + not clearlyNotNull(ssa) + ) + | + // Exclude fields as they might not have an accurate ssa representation. + not v.getVariable() instanceof Field and + firstVarDereferenceInBlock(bb, ssa, va) and + ssa.getSourceVariable() = v and + not exists(boolean branch | + nullGuard(ssa, branch, false).directlyControls(bb, branch) + ) + ) +} diff --git a/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll new file mode 100644 index 00000000000..152f240914d --- /dev/null +++ b/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll @@ -0,0 +1,227 @@ +/** + * Parity Analysis. + * + * The analysis is implemented as an abstract interpretation over the + * two-valued domain `{even, odd}`. + */ +import java +private import SSA +private import RangeUtils +private import semmle.code.java.controlflow.Guards +private import SignAnalysis +private import semmle.code.java.Reflection + +/** Gets an expression that is the remainder modulo 2 of `arg`. */ +private Expr mod2(Expr arg) { + exists(RemExpr rem | + result = rem and + arg = rem.getLeftOperand() and + rem.getRightOperand().(CompileTimeConstantExpr).getIntValue() = 2 + ) or + result.(AndBitwiseExpr).hasOperands(arg, any(CompileTimeConstantExpr c | c.getIntValue() = 1)) or + result.(ParExpr).getExpr() = mod2(arg) +} + +/** An expression that calculates remainder modulo 2. */ +private class Mod2 extends Expr { + Mod2() { + this = mod2(_) + } + + /** Gets the argument of this remainder operation. */ + Expr getArg() { + this = mod2(result) + } +} + +/** + * Parity represented as booleans. Even corresponds to `false` and odd + * corresponds to `true`. + */ +class Parity extends boolean { + Parity() { this = true or this = false } + predicate isEven() { this = false } + predicate isOdd() { this = true } +} + +/** + * Gets a condition that performs a parity check on `v`, such that `v` has + * the given parity if the condition evaluates to `testIsTrue`. + */ +private Guard parityCheck(SsaVariable v, Parity parity, boolean testIsTrue) { + exists(Mod2 rem, CompileTimeConstantExpr c, int r, boolean polarity | + result.isEquality(rem, c, polarity) and + c.getIntValue() = r and + (r = 0 or r = 1) and + rem.getArg() = v.getAUse() and + (testIsTrue = true or testIsTrue = false) and + ( + r = 0 and parity = testIsTrue.booleanXor(polarity) or + r = 1 and parity = testIsTrue.booleanXor(polarity).booleanNot() + ) + ) +} + +/** + * Gets the parity of `e` if it can be directly determined. + */ +private Parity certainExprParity(Expr e) { + exists(int i | e.(ConstantIntegerExpr).getIntValue() = i | + if i % 2 = 0 then result.isEven() else result.isOdd() + ) or + e.(LongLiteral).getValue().regexpMatch(".*[02468]") and result.isEven() or + e.(LongLiteral).getValue().regexpMatch(".*[13579]") and result.isOdd() or + not exists(e.(ConstantIntegerExpr).getIntValue()) and + ( + result = certainExprParity(e.(ParExpr).getExpr()) or + exists(Guard guard, SsaVariable v, boolean testIsTrue | + guard = parityCheck(v, result, testIsTrue) and + e = v.getAUse() and + guardControls_v2(guard, e.getBasicBlock(), testIsTrue) + ) or + exists(SsaVariable arr, int arrlen, FieldAccess len | + e = len and + len.getField() instanceof ArrayLengthField and + len.getQualifier() = arr.getAUse() and + arr.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getFirstDimensionSize() = arrlen and + if arrlen % 2 = 0 then result.isEven() else result.isOdd() + ) + ) +} + +/** + * Gets the expression that defines the array length that equals `len`, if any. + */ +private Expr arrLenDef(FieldAccess len) { + exists(SsaVariable arr | + len.getField() instanceof ArrayLengthField and + len.getQualifier() = arr.getAUse() and + arr.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getDimension(0) = result + ) +} + +/** Gets a possible parity for `v`. */ +private Parity ssaParity(SsaVariable v) { + exists(VariableUpdate def | def = v.(SsaExplicitUpdate).getDefiningExpr() | + result = exprParity(def.(VariableAssign).getSource()) or + exists(EnhancedForStmt for | def = for.getVariable()) and (result = true or result = false) or + result = exprParity(def.(UnaryAssignExpr).getExpr()).booleanNot() or + exists(AssignOp a | a = def and result = exprParity(a)) + ) or + result = fieldParity(v.(SsaImplicitUpdate).getSourceVariable().getVariable()) or + result = fieldParity(v.(SsaImplicitInit).getSourceVariable().getVariable()) or + exists(Parameter p | v.(SsaImplicitInit).isParameterDefinition(p) and (result = true or result = false)) or + result = ssaParity(v.(SsaPhiNode).getAPhiInput()) +} + +/** Gets a possible parity for `f`. */ +private Parity fieldParity(Field f) { + result = exprParity(f.getAnAssignedValue()) or + exists(UnaryAssignExpr u | u.getExpr() = f.getAnAccess() and (result = true or result = false)) or + exists(AssignOp a | a.getDest() = f.getAnAccess() | result = exprParity(a)) or + exists(ReflectiveFieldAccess rfa | rfa.inferAccessedField() = f and (result = true or result = false)) + or + if f.fromSource() then + not exists(f.getInitializer()) and result.isEven() + else + (result = true or result = false) +} + +/** Holds if the parity of `e` is too complicated to determine. */ +private predicate unknownParity(Expr e) { + e instanceof AssignDivExpr or + e instanceof AssignRShiftExpr or + e instanceof AssignURShiftExpr or + e instanceof DivExpr or + e instanceof RShiftExpr or + e instanceof URShiftExpr or + exists(Type fromtyp | e.(CastExpr).getExpr().getType() = fromtyp and not fromtyp instanceof IntegralType) or + e instanceof ArrayAccess and e.getType() instanceof IntegralType or + e instanceof MethodAccess and e.getType() instanceof IntegralType or + e instanceof ClassInstanceExpr and e.getType() instanceof IntegralType or + e.getType() instanceof FloatingPointType or + e.getType() instanceof CharacterType +} + +/** Gets a possible parity for `e`. */ +private Parity exprParity(Expr e) { + result = certainExprParity(e) or + not exists(certainExprParity(e)) and + ( + result = exprParity(e.(ParExpr).getExpr()) or + result = exprParity(arrLenDef(e)) or + exists(SsaVariable v | v.getAUse() = e | result = ssaParity(v)) and not exists(arrLenDef(e)) or + exists(FieldAccess fa | fa = e | + not exists(SsaVariable v | v.getAUse() = fa) and + not exists(arrLenDef(e)) and + result = fieldParity(fa.getField()) + ) or + exists(VarAccess va | va = e | + not exists(SsaVariable v | v.getAUse() = va) and + not va instanceof FieldAccess and + (result = true or result = false) + ) or + result = exprParity(e.(AssignExpr).getSource()) or + result = exprParity(e.(PlusExpr).getExpr()) or + result = exprParity(e.(PostIncExpr).getExpr()) or + result = exprParity(e.(PostDecExpr).getExpr()) or + result = exprParity(e.(PreIncExpr).getExpr()).booleanNot() or + result = exprParity(e.(PreDecExpr).getExpr()).booleanNot() or + result = exprParity(e.(MinusExpr).getExpr()) or + result = exprParity(e.(BitNotExpr).getExpr()).booleanNot() or + unknownParity(e) and (result = true or result = false) or + exists(Parity p1, Parity p2, AssignOp a | + a = e and + p1 = exprParity(a.getDest()) and + p2 = exprParity(a.getRhs()) + | + a instanceof AssignAddExpr and result = p1.booleanXor(p2) or + a instanceof AssignSubExpr and result = p1.booleanXor(p2) or + a instanceof AssignMulExpr and result = p1.booleanAnd(p2) or + a instanceof AssignRemExpr and (p2.isEven() and result = p1 or p2.isOdd() and (result = true or result = false)) or + a instanceof AssignAndExpr and result = p1.booleanAnd(p2) or + a instanceof AssignOrExpr and result = p1.booleanOr(p2) or + a instanceof AssignXorExpr and result = p1.booleanXor(p2) or + a instanceof AssignLShiftExpr and (result.isEven() or result = p1 and not strictlyPositive(a.getRhs())) + ) or + exists(Parity p1, Parity p2, BinaryExpr bin | + bin = e and + p1 = exprParity(bin.getLeftOperand()) and + p2 = exprParity(bin.getRightOperand()) + | + bin instanceof AddExpr and result = p1.booleanXor(p2) or + bin instanceof SubExpr and result = p1.booleanXor(p2) or + bin instanceof MulExpr and result = p1.booleanAnd(p2) or + bin instanceof RemExpr and (p2.isEven() and result = p1 or p2.isOdd() and (result = true or result = false)) or + bin instanceof AndBitwiseExpr and result = p1.booleanAnd(p2) or + bin instanceof OrBitwiseExpr and result = p1.booleanOr(p2) or + bin instanceof XorBitwiseExpr and result = p1.booleanXor(p2) or + bin instanceof LShiftExpr and (result.isEven() or result = p1 and not strictlyPositive(bin.getRightOperand())) + ) or + result = exprParity(e.(ConditionalExpr).getTrueExpr()) or + result = exprParity(e.(ConditionalExpr).getFalseExpr()) or + result = exprParity(e.(CastExpr).getExpr()) + ) +} + +/** + * Gets the parity of `e` if it can be uniquely determined. + */ +Parity getExprParity(Expr e) { + result = exprParity(e) and 1 = count(exprParity(e)) +} + +/** + * Holds if the parity can be determined for both sides of `comp`. The boolean + * `eqparity` indicates whether the two sides have equal or opposite parity. + */ +predicate parityComparison(ComparisonExpr comp, boolean eqparity) { + exists(Expr left, Expr right, boolean lpar, boolean rpar | + comp.getLeftOperand() = left and + comp.getRightOperand() = right and + lpar = getExprParity(left) and + rpar = getExprParity(right) and + eqparity = lpar.booleanXor(rpar).booleanNot() + ) +} + diff --git a/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll new file mode 100644 index 00000000000..d6d396e8c6a --- /dev/null +++ b/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll @@ -0,0 +1,708 @@ +/** + * Provides classes and predicates for range analysis. + * + * An inferred bound can either be a specific integer, the abstract value of an + * SSA variable, or the abstract value of an interesting expression. The latter + * category includes array lengths that are not SSA variables. + * + * If an inferred bound relies directly on a condition, then this condition is + * reported as the reason for the bound. + */ + +/* + * This library tackles range analysis as a flow problem. Consider e.g.: + * ``` + * len = arr.length; + * if (x < len) { ... y = x-1; ... y ... } + * ``` + * In this case we would like to infer `y <= arr.length - 2`, and this is + * accomplished by tracking the bound through a sequence of steps: + * ``` + * arr.length --> len = .. --> x < len --> x-1 --> y = .. --> y + * ``` + * + * In its simplest form the step relation `E1 --> E2` relates two expressions + * such that `E1 <= B` implies `E2 <= B` for any `B` (with a second separate + * step relation handling lower bounds). Examples of such steps include + * assignments `E2 = E1` and conditions `x <= E1` where `E2` is a use of `x` + * guarded by the condition. + * + * In order to handle subtractions and additions with constants, and strict + * comparisons, the step relation is augmented with an integer delta. With this + * generalization `E1 --(delta)--> E2` relates two expressions and an integer + * such that `E1 <= B` implies `E2 <= B + delta` for any `B`. This corresponds + * to the predicate `boundFlowStep`. + * + * The complete range analysis is then implemented as the transitive closure of + * the step relation summing the deltas along the way. If `E1` transitively + * steps to `E2`, `delta` is the sum of deltas along the path, and `B` is an + * interesting bound equal to the value of `E1` then `E2 <= B + delta`. This + * corresponds to the predicate `bounded`. + * + * Phi nodes need a little bit of extra handling. Consider `x0 = phi(x1, x2)`. + * There are essentially two cases: + * - If `x1 <= B + d1` and `x2 <= B + d2` then `x0 <= B + max(d1,d2)`. + * - If `x1 <= B + d1` and `x2 <= x0 + d2` with `d2 <= 0` then `x0 <= B + d1`. + * The first case is for whenever a bound can be proven without taking looping + * into account. The second case is relevant when `x2` comes from a back-edge + * where we can prove that the variable has been non-increasing through the + * loop-iteration as this means that any upper bound that holds prior to the + * loop also holds for the variable during the loop. + * This generalizes to a phi node with `n` inputs, so if + * `x0 = phi(x1, ..., xn)` and `xi <= B + delta` for one of the inputs, then we + * also have `x0 <= B + delta` if we can prove either: + * - `xj <= B + d` with `d <= delta` or + * - `xj <= x0 + d` with `d <= 0` + * for each input `xj`. + * + * As all inferred bounds can be related directly to a path in the source code + * the only source of non-termination is if successive redundant (and thereby + * increasingly worse) bounds are calculated along a loop in the source code. + * We prevent this by weakening the bound to a small finite set of bounds when + * a path follows a second back-edge (we postpone weakening till the second + * back-edge as a precise bound might require traversing a loop once). + */ + +import java +private import SSA +private import RangeUtils +private import semmle.code.java.controlflow.internal.GuardsLogic +private import SignAnalysis +private import ParityAnalysis +private import semmle.code.java.Reflection +private import semmle.code.java.Collections +private import semmle.code.java.Maps + +cached private module RangeAnalysisCache { + + cached module RangeAnalysisPublic { + /** + * Holds if `b + delta` is a valid bound for `e`. + * - `upper = true` : `e <= b + delta` + * - `upper = false` : `e >= b + delta` + * + * The reason for the bound is given by `reason` and may be either a condition + * or `NoReason` if the bound was proven directly without the use of a bounding + * condition. + */ + cached predicate bounded(Expr e, Bound b, int delta, boolean upper, Reason reason) { + bounded(e, b, delta, upper, _, _, reason) + } + } + + /** + * Holds if `guard = boundFlowCond(_, _, _, _, _) or guard = eqFlowCond(_, _, _, _, _)`. + */ + cached predicate possibleReason(Guard guard) { guard = boundFlowCond(_, _, _, _, _) or guard = eqFlowCond(_, _, _, _, _) } + +} +private import RangeAnalysisCache +import RangeAnalysisPublic + +/** + * Gets a condition that tests whether `v` equals `e + delta`. + * + * If the condition evaluates to `testIsTrue`: + * - `isEq = true` : `v == e + delta` + * - `isEq = false` : `v != e + delta` + */ +private Guard eqFlowCond(SsaVariable v, Expr e, int delta, boolean isEq, boolean testIsTrue) { + exists(boolean eqpolarity | + result.isEquality(ssaRead(v, delta), e, eqpolarity) and + (testIsTrue = true or testIsTrue = false) and + eqpolarity.booleanXor(testIsTrue).booleanNot() = isEq + ) + or + exists(boolean testIsTrue0 | implies_v2(result, testIsTrue, eqFlowCond(v, e, delta, isEq, testIsTrue0), testIsTrue0)) +} + +/** + * Holds if `comp` corresponds to: + * - `upper = true` : `v <= e + delta` or `v < e + delta` + * - `upper = false` : `v >= e + delta` or `v > e + delta` + */ +private predicate boundCondition(ComparisonExpr comp, SsaVariable v, Expr e, int delta, boolean upper) { + comp.getLesserOperand() = ssaRead(v, delta) and e = comp.getGreaterOperand() and upper = true + or + comp.getGreaterOperand() = ssaRead(v, delta) and e = comp.getLesserOperand() and upper = false + or + exists(SubExpr sub, ConstantIntegerExpr c, int d | + // (v - d) - e < c + comp.getLesserOperand().getProperExpr() = sub and comp.getGreaterOperand() = c and + sub.getLeftOperand() = ssaRead(v, d) and sub.getRightOperand() = e and + upper = true and delta = d + c.getIntValue() + or + // (v - d) - e > c + comp.getGreaterOperand().getProperExpr() = sub and comp.getLesserOperand() = c and + sub.getLeftOperand() = ssaRead(v, d) and sub.getRightOperand() = e and + upper = false and delta = d + c.getIntValue() + or + // e - (v - d) < c + comp.getLesserOperand().getProperExpr() = sub and comp.getGreaterOperand() = c and + sub.getLeftOperand() = e and sub.getRightOperand() = ssaRead(v, d) and + upper = false and delta = d - c.getIntValue() + or + // e - (v - d) > c + comp.getGreaterOperand().getProperExpr() = sub and comp.getLesserOperand() = c and + sub.getLeftOperand() = e and sub.getRightOperand() = ssaRead(v, d) and + upper = true and delta = d - c.getIntValue() + ) +} + +/** + * Gets a condition that tests whether `v` is bounded by `e + delta`. + * + * If the condition evaluates to `testIsTrue`: + * - `upper = true` : `v <= e + delta` + * - `upper = false` : `v >= e + delta` + */ +private Guard boundFlowCond(SsaVariable v, Expr e, int delta, boolean upper, boolean testIsTrue) { + exists(ComparisonExpr comp, int d1, int d2, int d3, int strengthen, boolean compIsUpper, boolean resultIsStrict | + comp = result and + boundCondition(comp, v, e, d1, compIsUpper) and + (testIsTrue = true or testIsTrue = false) and + upper = compIsUpper.booleanXor(testIsTrue.booleanNot()) and + (if comp.isStrict() then resultIsStrict = testIsTrue else resultIsStrict = testIsTrue.booleanNot()) and + (if v.getSourceVariable().getType() instanceof IntegralType then + (upper = true and strengthen = -1 or + upper = false and strengthen = 1) + else + strengthen = 0) and + // A non-strict inequality `x <= y` can be strengthened to `x <= y - 1` if + // `x` and `y` have opposite parities, and a strict inequality `x < y` can + // be similarly strengthened if `x` and `y` have equal parities. + (if parityComparison(comp, resultIsStrict) then d2 = strengthen else d2 = 0) and + // A strict inequality `x < y` can be strengthened to `x <= y - 1`. + (resultIsStrict = true and d3 = strengthen or resultIsStrict = false and d3 = 0) and + delta = d1 + d2 + d3 + ) or + exists(boolean testIsTrue0 | implies_v2(result, testIsTrue, boundFlowCond(v, e, delta, upper, testIsTrue0), testIsTrue0)) or + result = eqFlowCond(v, e, delta, true, testIsTrue) and (upper = true or upper = false) +} + +private newtype TReason = + TNoReason() or + TCondReason(Guard guard) { possibleReason(guard) } + +/** + * A reason for an inferred bound. This can either be `CondReason` if the bound + * is due to a specific condition, or `NoReason` if the bound is inferred + * without going through a bounding condition. + */ +abstract class Reason extends TReason { + abstract string toString(); +} +class NoReason extends Reason, TNoReason { + override string toString() { result = "NoReason" } +} +class CondReason extends Reason, TCondReason { + Guard getCond() { this = TCondReason(result) } + override string toString() { result = getCond().toString() } +} + +/** + * Holds if `e + delta` is a valid bound for `v` at `pos`. + * - `upper = true` : `v <= e + delta` + * - `upper = false` : `v >= e + delta` + */ +private predicate boundFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e, int delta, boolean upper, Reason reason) { + exists(SsaExplicitUpdate upd | v = upd and pos.hasReadOfVar(v) and reason = TNoReason() | + upd.getDefiningExpr().(VariableAssign).getSource() = e and delta = 0 and (upper = true or upper = false) or + upd.getDefiningExpr().(PostIncExpr).getExpr() = e and delta = 1 and (upper = true or upper = false) or + upd.getDefiningExpr().(PreIncExpr).getExpr() = e and delta = 1 and (upper = true or upper = false) or + upd.getDefiningExpr().(PostDecExpr).getExpr() = e and delta = -1 and (upper = true or upper = false) or + upd.getDefiningExpr().(PreDecExpr).getExpr() = e and delta = -1 and (upper = true or upper = false) or + upd.getDefiningExpr().(AssignOp) = e and delta = 0 and (upper = true or upper = false) + ) or + exists(Guard guard, boolean testIsTrue | + pos.hasReadOfVar(v) and + guard = boundFlowCond(v, e, delta, upper, testIsTrue) and + guardDirectlyControlsSsaRead(guard, pos, testIsTrue) and + reason = TCondReason(guard) + ) +} + +/** Holds if `v != e + delta` at `pos`. */ +private predicate unequalFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e, int delta, Reason reason) { + exists(Guard guard, boolean testIsTrue | + pos.hasReadOfVar(v) and + guard = eqFlowCond(v, e, delta, false, testIsTrue) and + guardDirectlyControlsSsaRead(guard, pos, testIsTrue) and + reason = TCondReason(guard) + ) +} + +/** + * Holds if a cast from `fromtyp` to `totyp` can be ignored for the purpose of + * range analysis. + */ +private predicate safeCast(Type fromtyp, Type totyp) { + exists(PrimitiveType pfrom, PrimitiveType pto | pfrom = fromtyp and pto = totyp | + pfrom = pto or + pfrom.hasName("char") and pto.getName().regexpMatch("int|long|float|double") or + pfrom.hasName("byte") and pto.getName().regexpMatch("short|int|long|float|double") or + pfrom.hasName("short") and pto.getName().regexpMatch("int|long|float|double") or + pfrom.hasName("int") and pto.getName().regexpMatch("long|float|double") or + pfrom.hasName("long") and pto.getName().regexpMatch("float|double") or + pfrom.hasName("float") and pto.hasName("double") or + pfrom.hasName("double") and pto.hasName("float") + ) or + safeCast(fromtyp.(BoxedType).getPrimitiveType(), totyp) or + safeCast(fromtyp, totyp.(BoxedType).getPrimitiveType()) +} + +/** + * A cast that can be ignored for the purpose of range analysis. + */ +private class SafeCastExpr extends CastExpr { + SafeCastExpr() { + safeCast(getExpr().getType(), getType()) + } +} + +/** + * Holds if `typ` is a small integral type with the given lower and upper bounds. + */ +private predicate typeBound(Type typ, int lowerbound, int upperbound) { + typ.(PrimitiveType).hasName("byte") and lowerbound = -128 and upperbound = 127 or + typ.(PrimitiveType).hasName("short") and lowerbound = -32768 and upperbound = 32767 or + typ.(PrimitiveType).hasName("char") and lowerbound = 0 and upperbound = 65535 or + typeBound(typ.(BoxedType).getPrimitiveType(), lowerbound, upperbound) +} + +/** + * A cast to a small integral type that may overflow or underflow. + */ +private class NarrowingCastExpr extends CastExpr { + NarrowingCastExpr() { + not this instanceof SafeCastExpr and + typeBound(getType(), _, _) + } + /** Gets the lower bound of the resulting type. */ + int getLowerBound() { typeBound(getType(), result, _) } + /** Gets the upper bound of the resulting type. */ + int getUpperBound() { typeBound(getType(), _, result) } +} + +/** + * Holds if `e1 + delta` is a valid bound for `e2`. + * - `upper = true` : `e2 <= e1 + delta` + * - `upper = false` : `e2 >= e1 + delta` + */ +private predicate boundFlowStep(Expr e2, Expr e1, int delta, boolean upper) { + e2.(ParExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or + e2.(AssignExpr).getSource() = e1 and delta = 0 and (upper = true or upper = false) or + e2.(PlusExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or + e2.(PostIncExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or + e2.(PostDecExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or + e2.(PreIncExpr).getExpr() = e1 and delta = 1 and (upper = true or upper = false) or + e2.(PreDecExpr).getExpr() = e1 and delta = -1 and (upper = true or upper = false) or + e2.(SafeCastExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or + exists(SsaExplicitUpdate v, FieldRead arrlen | + e2 = arrlen and + arrlen.getField() instanceof ArrayLengthField and + arrlen.getQualifier() = v.getAUse() and + v.getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getDimension(0) = e1 and + delta = 0 and + (upper = true or upper = false) + ) or + exists(Expr x | + e2.(AddExpr).hasOperands(e1, x) or + exists(AssignAddExpr add | add = e2 | + add.getDest() = e1 and add.getRhs() = x or + add.getDest() = x and add.getRhs() = e1 + ) + | + x.(ConstantIntegerExpr).getIntValue() = delta and (upper = true or upper = false) + or + not x instanceof ConstantIntegerExpr and + not e1 instanceof ConstantIntegerExpr and + if strictlyPositive(x) then + (upper = false and delta = 1) + else if positive(x) then + (upper = false and delta = 0) + else if strictlyNegative(x) then + (upper = true and delta = -1) + else if negative(x) then + (upper = true and delta = 0) + else + none() + ) or + exists(Expr x | + exists(SubExpr sub | + e2 = sub and + sub.getLeftOperand() = e1 and + sub.getRightOperand() = x + ) or + exists(AssignSubExpr sub | + e2 = sub and + sub.getDest() = e1 and + sub.getRhs() = x + ) + | + x.(ConstantIntegerExpr).getIntValue() = -delta and (upper = true or upper = false) + or + not x instanceof ConstantIntegerExpr and + if strictlyPositive(x) then + (upper = true and delta = -1) + else if positive(x) then + (upper = true and delta = 0) + else if strictlyNegative(x) then + (upper = false and delta = 1) + else if negative(x) then + (upper = false and delta = 0) + else + none() + ) or + e2.(RemExpr).getRightOperand() = e1 and positive(e1) and delta = -1 and upper = true or + e2.(RemExpr).getLeftOperand() = e1 and positive(e1) and delta = 0 and upper = true or + e2.(AssignRemExpr).getRhs() = e1 and positive(e1) and delta = -1 and upper = true or + e2.(AssignRemExpr).getDest() = e1 and positive(e1) and delta = 0 and upper = true or + e2.(AndBitwiseExpr).getAnOperand() = e1 and positive(e1) and delta = 0 and upper = true or + e2.(AssignAndExpr).getSource() = e1 and positive(e1) and delta = 0 and upper = true or + e2.(OrBitwiseExpr).getAnOperand() = e1 and positive(e2) and delta = 0 and upper = false or + e2.(AssignOrExpr).getSource() = e1 and positive(e2) and delta = 0 and upper = false or + exists(MethodAccess ma, Method m | + e2 = ma and + ma.getMethod() = m and + m.hasName("nextInt") and + m.getDeclaringType().hasQualifiedName("java.util", "Random") and + e1 = ma.getAnArgument() and + delta = -1 and + upper = true + ) or + exists(MethodAccess ma, Method m | + e2 = ma and + ma.getMethod() = m and + (m.hasName("max") and upper = false or m.hasName("min") and upper = true) and + m.getDeclaringType().hasQualifiedName("java.lang", "Math") and + e1 = ma.getAnArgument() and + delta = 0 + ) +} + +/** Holds if `e2 = e1 * factor` and `factor > 0`. */ +private predicate boundFlowStepMul(Expr e2, Expr e1, int factor) { + exists(ConstantIntegerExpr c, int k | k = c.getIntValue() and k > 0 | + e2.(MulExpr).hasOperands(e1, c) and factor = k or + exists(AssignMulExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = k) or + exists(AssignMulExpr e | e = e2 and e.getDest() = c and e.getRhs() = e1 and factor = k) or + exists(LShiftExpr e | e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)) or + exists(AssignLShiftExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k)) + ) +} + +/** + * Holds if `e2 = e1 / factor` and `factor > 0`. + * + * This conflates division, right shift, and unsigned right shift and is + * therefore only valid for non-negative numbers. + */ +private predicate boundFlowStepDiv(Expr e2, Expr e1, int factor) { + exists(ConstantIntegerExpr c, int k | k = c.getIntValue() and k > 0 | + exists(DivExpr e | e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = k) or + exists(AssignDivExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = k) or + exists(RShiftExpr e | e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)) or + exists(AssignRShiftExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k)) or + exists(URShiftExpr e | e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)) or + exists(AssignURShiftExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k)) + ) +} + +private newtype TBound = + TBoundZero() or + TBoundSsa(SsaVariable v) { v.getSourceVariable().getType() instanceof IntegralType } or + TBoundExpr(Expr e) { e.(FieldRead).getField() instanceof ArrayLengthField and not exists(SsaVariable v | e = v.getAUse()) } + +/** + * A bound that may be inferred for an expression plus/minus an integer delta. + */ +abstract class Bound extends TBound { + abstract string toString(); + /** Gets an expression that equals this bound plus `delta`. */ + abstract Expr getExpr(int delta); + /** Gets an expression that equals this bound. */ + Expr getExpr() { + result = getExpr(0) + } + predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { + path = "" and sl = 0 and sc = 0 and el = 0 and ec = 0 + } +} + +/** + * The bound that corresponds to the integer 0. This is used to represent all + * integer bounds as bounds are always accompanied by an added integer delta. + */ +class ZeroBound extends Bound, TBoundZero { + override string toString() { result = "0" } + override Expr getExpr(int delta) { result.(ConstantIntegerExpr).getIntValue() = delta } +} + +/** + * A bound corresponding to the value of an SSA variable. + */ +class SsaBound extends Bound, TBoundSsa { + /** Gets the SSA variable that equals this bound. */ + SsaVariable getSsa() { this = TBoundSsa(result) } + override string toString() { result = getSsa().toString() } + override Expr getExpr(int delta) { result = getSsa().getAUse() and delta = 0 } + override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { + getSsa().getLocation().hasLocationInfo(path, sl, sc, el, ec) + } +} + +/** + * A bound that corresponds to the value of a specific expression that might be + * interesting, but isn't otherwise represented by the value of an SSA variable. + */ +class ExprBound extends Bound, TBoundExpr { + override string toString() { result = getExpr().toString() } + override Expr getExpr(int delta) { this = TBoundExpr(result) and delta = 0 } + override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { + getExpr().hasLocationInfo(path, sl, sc, el, ec) + } +} + +/** + * Holds if `b + delta` is a valid bound for `v` at `pos`. + * - `upper = true` : `v <= b + delta` + * - `upper = false` : `v >= b + delta` + */ +private predicate boundedSsa(SsaVariable v, SsaReadPosition pos, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) { + exists(Expr mid, int d1, int d2, Reason r1, Reason r2 | + boundFlowStepSsa(v, pos, mid, d1, upper, r1) and + bounded(mid, b, d2, upper, fromBackEdge, origdelta, r2) and + // upper = true: v <= mid + d1 <= b + d1 + d2 = b + delta + // upper = false: v >= mid + d1 >= b + d1 + d2 = b + delta + delta = d1 + d2 and + (if r1 instanceof NoReason then reason = r2 else reason = r1) + ) or + exists(int d, Reason r1, Reason r2 | + boundedSsa(v, pos, b, d, upper, fromBackEdge, origdelta, r2) or + boundedPhi(v, b, d, upper, fromBackEdge, origdelta, r2) + | + unequalSsa(v, pos, b, d, r1) and + (upper = true and delta = d - 1 or upper = false and delta = d + 1) and + (reason = r1 or reason = r2 and not r2 instanceof NoReason) + ) +} + +/** + * Holds if `v != b + delta` at `pos`. + */ +private predicate unequalSsa(SsaVariable v, SsaReadPosition pos, Bound b, int delta, Reason reason) { + exists(Expr e, int d1, int d2 | + unequalFlowStepSsa(v, pos, e, d1, reason) and + bounded(e, b, d2, true, _, _, _) and + bounded(e, b, d2, false, _, _, _) and + delta = d2 + d1 + ) +} + +/** + * Holds if `inp` is an input to `phi` along a back edge. + */ +private predicate backEdge(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge) { + edge.phiInput(phi, inp) and + // Conservatively assume that every edge is a back edge if we don't have dominance information. + (phi.getBasicBlock().bbDominates(edge.getOrigBlock()) or not hasDominanceInformation(edge.getOrigBlock())) +} + +/** Weakens a delta to lie in the range `[-1..1]`. */ +bindingset[delta, upper] +private int weakenDelta(boolean upper, int delta) { + delta in [-1..1] and result = delta or + upper = true and result = -1 and delta < -1 or + upper = false and result = 1 and delta > 1 +} + +/** + * Holds if `b + delta` is a valid bound for `inp` when used as an input to + * `phi` along `edge`. + * - `upper = true` : `inp <= b + delta` + * - `upper = false` : `inp >= b + delta` + */ +private predicate boundedPhiInp(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) { + edge.phiInput(phi, inp) and + exists(int d, boolean fromBackEdge0 | + boundedSsa(inp, edge, b, d, upper, fromBackEdge0, origdelta, reason) or + boundedPhi(inp, b, d, upper, fromBackEdge0, origdelta, reason) or + b.(SsaBound).getSsa() = inp and d = 0 and (upper = true or upper = false) and fromBackEdge0 = false and origdelta = 0 and reason = TNoReason() + | + if backEdge(phi, inp, edge) then + fromBackEdge = true and + ( + fromBackEdge0 = true and delta = weakenDelta(upper, d - origdelta) + origdelta or + fromBackEdge0 = false and delta = d + ) + else + (delta = d and fromBackEdge = fromBackEdge0) + ) +} + +/** Holds if `boundedPhiInp(phi, inp, edge, b, delta, upper, _, _, _)`. */ +pragma[noinline] +private predicate boundedPhiInp1(SsaPhiNode phi, Bound b, boolean upper, SsaVariable inp, SsaReadPositionPhiInputEdge edge, int delta) { + boundedPhiInp(phi, inp, edge, b, delta, upper, _, _, _) +} + +/** + * Holds if `phi` is a valid bound for `inp` when used as an input to `phi` + * along `edge`. + * - `upper = true` : `inp <= phi` + * - `upper = false` : `inp >= phi` + */ +private predicate selfBoundedPhiInp(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, boolean upper) { + exists(int d, SsaBound phibound | + phibound.getSsa() = phi and + boundedPhiInp(phi, inp, edge, phibound, d, upper, _, _, _) and + (upper = true and d <= 0 or upper = false and d >= 0) + ) +} + +/** + * Holds if `b + delta` is a valid bound for some input, `inp`, to `phi`, and + * thus a candidate bound for `phi`. + * - `upper = true` : `inp <= b + delta` + * - `upper = false` : `inp >= b + delta` + */ +pragma[noinline] +private predicate boundedPhiCand(SsaPhiNode phi, boolean upper, Bound b, int delta, boolean fromBackEdge, int origdelta, Reason reason) { + exists(SsaVariable inp, SsaReadPositionPhiInputEdge edge | boundedPhiInp(phi, inp, edge, b, delta, upper, fromBackEdge, origdelta, reason)) +} + +/** + * Holds if the candidate bound `b + delta` for `phi` is valid for the phi input + * `inp` along `edge`. + */ +private predicate boundedPhiCandValidForEdge(SsaPhiNode phi, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason, SsaVariable inp, SsaReadPositionPhiInputEdge edge) { + boundedPhiCand(phi, upper, b, delta, fromBackEdge, origdelta, reason) and + ( + exists(int d | boundedPhiInp1(phi, b, upper, inp, edge, d) | upper = true and d <= delta) or + exists(int d | boundedPhiInp1(phi, b, upper, inp, edge, d) | upper = false and d >= delta) or + selfBoundedPhiInp(phi, inp, edge, upper) + ) +} + +/** + * Holds if `b + delta` is a valid bound for `phi`. + * - `upper = true` : `phi <= b + delta` + * - `upper = false` : `phi >= b + delta` + */ +private predicate boundedPhi(SsaPhiNode phi, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) { + forex(SsaVariable inp, SsaReadPositionPhiInputEdge edge | edge.phiInput(phi, inp) | + boundedPhiCandValidForEdge(phi, b, delta, upper, fromBackEdge, origdelta, reason, inp, edge) + ) +} + +/** + * Holds if `e` has a lower bound of zero. + */ +private predicate lowerBoundZero(Expr e) { + e.(MethodAccess).getMethod() instanceof StringLengthMethod or + e.(MethodAccess).getMethod() instanceof CollectionSizeMethod or + e.(MethodAccess).getMethod() instanceof MapSizeMethod or + e.(FieldRead).getField() instanceof ArrayLengthField or + positive(e.(AndBitwiseExpr).getAnOperand()) +} + +/** + * Holds if `e` has an upper (for `upper = true`) or lower + * (for `upper = false`) bound of `b`. + */ +private predicate baseBound(Expr e, int b, boolean upper) { + lowerBoundZero(e) and b = 0 and upper = false or + exists(Method read | + e.(MethodAccess).getMethod().overrides*(read) and + read.getDeclaringType().hasQualifiedName("java.io", "InputStream") and + read.hasName("read") and + read.getNumberOfParameters() = 0 + | + upper = true and b = 255 or + upper = false and b = -1 + ) +} + +/** + * Holds if the value being cast has an upper (for `upper = true`) or lower + * (for `upper = false`) bound within the bounds of the resulting type. + * For `upper = true` this means that the cast will not overflow and for + * `upper = false` this means that the cast will not underflow. + */ +private predicate safeNarrowingCast(NarrowingCastExpr cast, boolean upper) { + exists(int bound | + bounded(cast.getExpr(), TBoundZero(), bound, upper, _, _, _) + | + upper = true and bound <= cast.getUpperBound() or + upper = false and bound >= cast.getLowerBound() + ) +} + +pragma[noinline] +private predicate boundedCastExpr(NarrowingCastExpr cast, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) { + bounded(cast.getExpr(), b, delta, upper, fromBackEdge, origdelta, reason) +} + +/** + * Holds if `b + delta` is a valid bound for `e`. + * - `upper = true` : `e <= b + delta` + * - `upper = false` : `e >= b + delta` + */ +private predicate bounded(Expr e, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) { + e = b.getExpr(delta) and (upper = true or upper = false) and fromBackEdge = false and origdelta = delta and reason = TNoReason() or + baseBound(e, delta, upper) and b instanceof ZeroBound and fromBackEdge = false and origdelta = delta and reason = TNoReason() or + exists(SsaVariable v, SsaReadPositionBlock bb | + boundedSsa(v, bb, b, delta, upper, fromBackEdge, origdelta, reason) and + e = v.getAUse() and + bb.getBlock() = e.getBasicBlock() + ) or + exists(Expr mid, int d1, int d2 | + boundFlowStep(e, mid, d1, upper) and + // Constants have easy, base-case bounds, so let's not infer any recursive bounds. + not e instanceof ConstantIntegerExpr and + bounded(mid, b, d2, upper, fromBackEdge, origdelta, reason) and + // upper = true: e <= mid + d1 <= b + d1 + d2 = b + delta + // upper = false: e >= mid + d1 >= b + d1 + d2 = b + delta + delta = d1 + d2 + ) or + exists(SsaPhiNode phi | + boundedPhi(phi, b, delta, upper, fromBackEdge, origdelta, reason) and + e = phi.getAUse() + ) or + exists(Expr mid, int factor, int d | + boundFlowStepMul(e, mid, factor) and + not e instanceof ConstantIntegerExpr and + bounded(mid, b, d, upper, fromBackEdge, origdelta, reason) and + b instanceof ZeroBound and + delta = d * factor + ) or + exists(Expr mid, int factor, int d | + boundFlowStepDiv(e, mid, factor) and + not e instanceof ConstantIntegerExpr and + bounded(mid, b, d, upper, fromBackEdge, origdelta, reason) and + b instanceof ZeroBound and + d >= 0 and + delta = d / factor + ) or + exists(NarrowingCastExpr cast | + cast = e and + safeNarrowingCast(cast, upper.booleanNot()) and + boundedCastExpr(cast, b, delta, upper, fromBackEdge, origdelta, reason) + ) or + exists(ConditionalExpr cond, int d1, int d2, boolean fbe1, boolean fbe2, int od1, int od2, Reason r1, Reason r2 | + cond = e and + boundedConditionalExpr(cond, b, upper, true, d1, fbe1, od1, r1) and + boundedConditionalExpr(cond, b, upper, false, d2, fbe2, od2, r2) and + (delta = d1 and fromBackEdge = fbe1 and origdelta = od1 and reason = r1 or + delta = d2 and fromBackEdge = fbe2 and origdelta = od2 and reason = r2) + | + upper = true and delta = d1.maximum(d2) or + upper = false and delta = d1.minimum(d2) + ) +} + +private predicate boundedConditionalExpr(ConditionalExpr cond, Bound b, boolean upper, boolean branch, int delta, boolean fromBackEdge, int origdelta, Reason reason) { + branch = true and bounded(cond.getTrueExpr(), b, delta, upper, fromBackEdge, origdelta, reason) or + branch = false and bounded(cond.getFalseExpr(), b, delta, upper, fromBackEdge, origdelta, reason) +} diff --git a/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll b/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll new file mode 100644 index 00000000000..9b5e9a257a2 --- /dev/null +++ b/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll @@ -0,0 +1,133 @@ +/** + * Provides utility predicates for range analysis. + */ + +import java +private import SSA +private import semmle.code.java.controlflow.internal.GuardsLogic + +/** An expression that always has the same integer value. */ +pragma[nomagic] +private predicate constantIntegerExpr(Expr e, int val) { + e.(CompileTimeConstantExpr).getIntValue() = val or + exists(SsaExplicitUpdate v, Expr src | + e = v.getAUse() and + src = v.getDefiningExpr().(VariableAssign).getSource() and + constantIntegerExpr(src, val) + ) +} + +/** An expression that always has the same integer value. */ +class ConstantIntegerExpr extends Expr { + ConstantIntegerExpr() { + constantIntegerExpr(this, _) + } + + /** Gets the integer value of this expression. */ + int getIntValue() { + constantIntegerExpr(this, result) + } +} + +/** + * Gets an expression that equals `v - d`. + */ +Expr ssaRead(SsaVariable v, int delta) { + result = v.getAUse() and delta = 0 or + result.(ParExpr).getExpr() = ssaRead(v, delta) or + exists(int d1, ConstantIntegerExpr c | + result.(AddExpr).hasOperands(ssaRead(v, d1), c) and + delta = d1 - c.getIntValue() + ) or + exists(SubExpr sub, int d1, ConstantIntegerExpr c | + result = sub and + sub.getLeftOperand() = ssaRead(v, d1) and + sub.getRightOperand() = c and + delta = d1 + c.getIntValue() + ) or + v.(SsaExplicitUpdate).getDefiningExpr().(PreIncExpr) = result and delta = 0 or + v.(SsaExplicitUpdate).getDefiningExpr().(PreDecExpr) = result and delta = 0 or + v.(SsaExplicitUpdate).getDefiningExpr().(PostIncExpr) = result and delta = 1 or // x++ === ++x - 1 + v.(SsaExplicitUpdate).getDefiningExpr().(PostDecExpr) = result and delta = -1 or // x-- === --x + 1 + v.(SsaExplicitUpdate).getDefiningExpr().(Assignment) = result and delta = 0 or + result.(AssignExpr).getSource() = ssaRead(v, delta) +} + +private newtype TSsaReadPosition = + TSsaReadPositionBlock(BasicBlock bb) { exists(SsaVariable v | bb = v.getAUse().getBasicBlock()) } or + TSsaReadPositionPhiInputEdge(BasicBlock bbOrig, BasicBlock bbPhi) { + exists(SsaPhiNode phi | phi.hasInputFromBlock(_, bbOrig) and bbPhi = phi.getBasicBlock()) + } + +/** + * A position at which an SSA variable is read. This includes both ordinary + * reads occurring in basic blocks and input to phi nodes occurring along an + * edge between two basic blocks. + */ +class SsaReadPosition extends TSsaReadPosition { + /** Holds if `v` is read at this position. */ + abstract predicate hasReadOfVar(SsaVariable v); + + abstract string toString(); +} + +/** A basic block in which an SSA variable is read. */ +class SsaReadPositionBlock extends SsaReadPosition, TSsaReadPositionBlock { + /** Gets the basic block corresponding to this position. */ + BasicBlock getBlock() { this = TSsaReadPositionBlock(result) } + + override predicate hasReadOfVar(SsaVariable v) { getBlock() = v.getAUse().getBasicBlock() } + + override string toString() { result = "block" } +} + +/** + * An edge between two basic blocks where the latter block has an SSA phi + * definition. The edge therefore has a read of an SSA variable serving as the + * input to the phi node. + */ +class SsaReadPositionPhiInputEdge extends SsaReadPosition, TSsaReadPositionPhiInputEdge { + /** Gets the head of the edge. */ + BasicBlock getOrigBlock() { this = TSsaReadPositionPhiInputEdge(result, _) } + + /** Gets the tail of the edge. */ + BasicBlock getPhiBlock() { this = TSsaReadPositionPhiInputEdge(_, result) } + + override predicate hasReadOfVar(SsaVariable v) { + exists(SsaPhiNode phi | + phi.hasInputFromBlock(v, getOrigBlock()) and + getPhiBlock() = phi.getBasicBlock() + ) + } + + /** Holds if `inp` is an input to `phi` along this edge. */ + predicate phiInput(SsaPhiNode phi, SsaVariable inp) { + phi.hasInputFromBlock(inp, getOrigBlock()) and + getPhiBlock() = phi.getBasicBlock() + } + + override string toString() { result = "edge" } +} + +/** + * Holds if `guard` directly controls the position `controlled` with the + * value `testIsTrue`. + */ +predicate guardDirectlyControlsSsaRead(Guard guard, SsaReadPosition controlled, boolean testIsTrue) { + guard.directlyControls(controlled.(SsaReadPositionBlock).getBlock(), testIsTrue) or + exists(SsaReadPositionPhiInputEdge controlledEdge | controlledEdge = controlled | + guard.directlyControls(controlledEdge.getOrigBlock(), testIsTrue) or + guard.hasBranchEdge(controlledEdge.getOrigBlock(), controlledEdge.getPhiBlock(), testIsTrue) + ) +} + +/** + * Holds if `guard` controls the position `controlled` with the value `testIsTrue`. + */ +predicate guardControlsSsaRead(Guard guard, SsaReadPosition controlled, boolean testIsTrue) { + guardDirectlyControlsSsaRead(guard, controlled, testIsTrue) or + exists(Guard guard0, boolean testIsTrue0 | + implies_v2(guard0, testIsTrue0, guard, testIsTrue) and + guardControlsSsaRead(guard0, controlled, testIsTrue0) + ) +} diff --git a/java/ql/src/semmle/code/java/dataflow/SSA.qll b/java/ql/src/semmle/code/java/dataflow/SSA.qll new file mode 100644 index 00000000000..2266c7034ac --- /dev/null +++ b/java/ql/src/semmle/code/java/dataflow/SSA.qll @@ -0,0 +1,1104 @@ +/** + * Provides classes and predicates for SSA representation (Static Single Assignment form). + * + * An SSA variable consists of the pair of a `SsaSourceVariable` and a + * `ControlFlowNode` at which it is defined. Each SSA variable is defined + * either by a phi node, an implicit initial value (for parameters and fields), + * an explicit update, or an implicit update (for fields). + * An implicit update occurs either at a `Call` that might modify a field, at + * another update that can update the qualifier of a field, or at a `FieldRead` + * of the field in case the field is not amenable to a non-trivial SSA + * representation. + */ + +import java +private import semmle.code.java.dispatch.VirtualDispatch +private import semmle.code.java.dispatch.WrappedInvocation + +private predicate fieldAccessInCallable(FieldAccess fa, Field f, Callable c) { + f = fa.getField() and + c = fa.getEnclosingCallable() +} + +cached +private newtype TSsaSourceVariable = + TLocalVar(Callable c, LocalScopeVariable v) { c = v.getCallable() or c = v.getAnAccess().getEnclosingCallable() } or + TPlainField(Callable c, Field f) { + exists(FieldRead fr | fieldAccessInCallable(fr, f, c) and (fr.isOwnFieldAccess() or f.isStatic())) + } or + TEnclosingField(Callable c, Field f, RefType t) { + exists(FieldRead fr | fieldAccessInCallable(fr, f, c) and fr.isEnclosingFieldAccess(t)) + } or + TQualifiedField(Callable c, SsaSourceVariable q, InstanceField f) { + exists(FieldRead fr | fieldAccessInCallable(fr, f, c) and fr.getQualifier() = q.getAnAccess()) + } + +/** + * A fully qualified variable in the context of a `Callable` in which it is + * accessed. + * + * This is either a local variable or a fully qualified field, `q.f1.f2....fn`, + * where the base qualifier `q` is either `this`, a local variable, or a type + * in case `f1` is static. + */ +class SsaSourceVariable extends TSsaSourceVariable { + /** Gets the variable corresponding to this `SsaSourceVariable`. */ + Variable getVariable() { + this = TLocalVar(_, result) or + this = TPlainField(_, result) or + this = TEnclosingField(_, result, _) or + this = TQualifiedField(_, _, result) + } + + /** + * Gets an access of this `SsaSourceVariable`. This access is within + * `this.getEnclosingCallable()`. Note that `LocalScopeVariable`s that are + * accessed from nested callables are therefore associated with several + * `SsaSourceVariable`s. + */ + cached + VarAccess getAnAccess() { + exists(LocalScopeVariable v, Callable c | this = TLocalVar(c, v) and result = v.getAnAccess() and result.getEnclosingCallable() = c) or + exists(Field f, Callable c | fieldAccessInCallable(result, f, c) | + (result.(FieldAccess).isOwnFieldAccess() or f.isStatic()) and this = TPlainField(c, f) + or + exists(RefType t | this = TEnclosingField(c, f, t) and result.(FieldAccess).isEnclosingFieldAccess(t)) + or + exists(SsaSourceVariable q | result.getQualifier() = q.getAnAccess() and this = TQualifiedField(c, q, f)) + ) + } + + /** Gets the `Callable` in which this `SsaSourceVariable` is defined. */ + Callable getEnclosingCallable() { + this = TLocalVar(result, _) or + this = TPlainField(result, _) or + this = TEnclosingField(result, _, _) or + this = TQualifiedField(result, _, _) + } + + string toString() { + exists(LocalScopeVariable v, Callable c | this = TLocalVar(c, v) | + if c = v.getCallable() then result = v.getName() else result = c.getName() + "(..)." + v.getName() + ) or + result = this.(SsaSourceField).ppQualifier() + "." + getVariable().toString() + } + + /** + * Gets the first access to `this` in terms of source code location. This is + * used as the representative location for named fields that otherwise would + * not have a specific source code location. + */ + private VarAccess getFirstAccess() { + result = min(this.getAnAccess() as a order by + a.getLocation().getStartLine(), a.getLocation().getStartColumn()) + } + + Location getLocation() { + exists(LocalScopeVariable v | this = TLocalVar(_, v) and result = v.getLocation()) or + this instanceof SsaSourceField and result = getFirstAccess().getLocation() + } + + /** Gets the type of this variable. */ + Type getType() { + result = this.getVariable().getType() + } + + /** Gets the qualifier, if any. */ + SsaSourceVariable getQualifier() { + this = TQualifiedField(_, result, _) + } + + /** Gets an SSA variable that has this variable as its underlying source variable. */ + SsaVariable getAnSsaVariable() { result.getSourceVariable() = this } +} + +/** + * A fully qualified field in the context of a `Callable` in which it is + * accessed. + */ +class SsaSourceField extends SsaSourceVariable { + SsaSourceField() { + this = TPlainField(_, _) or this = TEnclosingField(_, _, _) or this = TQualifiedField(_, _, _) + } + + /** Gets the field corresponding to this named field. */ + Field getField() { + result = getVariable() + } + + /** Gets a string representation of the qualifier. */ + string ppQualifier() { + exists(Field f | this = TPlainField(_, f) | + if f.isStatic() then + result = f.getDeclaringType().getQualifiedName() + else + result = "this" + ) + or + exists(Field f, RefType t | this = TEnclosingField(_, f, t) | + result = t.toString() + ".this" + ) + or + exists(SsaSourceVariable q | this = TQualifiedField(_, q, _) | + result = q.toString() + ) + } + + /** Holds if the field itself or any of the fields part of the qualifier are volatile. */ + predicate isVolatile() { + getField().isVolatile() or + getQualifier().(SsaSourceField).isVolatile() + } +} + +private module TrackedVariablesImpl { + + /** Gets the number of accesses of `f`. */ + private int numberOfAccesses(SsaSourceField f) { + result = strictcount(FieldAccess fa | fa = f.getAnAccess()) + } + + /** Holds if `f` is accessed inside a loop. */ + private predicate loopAccessed(SsaSourceField f) { + exists(LoopStmt l, FieldRead fr | fr = f.getAnAccess() | + l.getBody() = fr.getEnclosingStmt().getParent*() or + l.getCondition() = fr.getParent*() or + l.(ForStmt).getAnUpdate() = fr.getParent*() + ) + } + + /** Holds if `f` is accessed more than once or inside a loop. */ + private predicate multiAccessed(SsaSourceField f) { + loopAccessed(f) or 1 < numberOfAccesses(f) + } + + /** + * Holds if `f` is a field that is interesting as a basis for SSA. + * + * - A field that is read twice is interesting as we want to know whether the + * reads refer to the same value. + * - A field that is both written and read is interesting as we want to know + * whether the read might get the written value. + * - A field that is read in a loop is interesting as we want to know whether + * the value is the same in different iterations (that is, whether the SSA + * definition can be placed outside the loop). + * - A volatile field is never interesting, since all reads must reread from + * memory and we are forced to assume that the value can change at any point. + */ + cached + predicate trackField(SsaSourceField f) { + multiAccessed(f) and not f.isVolatile() + } + + /** + * The variables that form the basis of the non-trivial SSA construction. + * Fields that aren't tracked get a trivial SSA construction (a definition + * prior to every read). + */ + class TrackedVar extends SsaSourceVariable { + TrackedVar() { + this = TLocalVar(_, _) or + trackField(this) + } + } + + class TrackedField extends TrackedVar, SsaSourceField { + } + +} +private import TrackedVariablesImpl + +private cached module SsaImpl { + + /** Gets the destination variable of an update of a tracked variable. */ + cached + TrackedVar getDestVar(VariableUpdate upd) { + result.getAnAccess() = upd.(Assignment).getDest() or + exists(LocalVariableDecl v | v = upd.(LocalVariableDeclExpr).getVariable() | + result = TLocalVar(v.getCallable(), v) + ) or + result.getAnAccess() = upd.(UnaryAssignExpr).getExpr() + } + + /** Holds if `n` must update the locally tracked variable `v`. */ + cached + predicate certainVariableUpdate(TrackedVar v, ControlFlowNode n, BasicBlock b, int i) { + exists(VariableUpdate a | a = n | getDestVar(a) = v) and b.getNode(i) = n or + certainVariableUpdate(v.getQualifier(), n, b, i) + } + + /** Gets the definition point of a nested class in the parent scope. */ + private ControlFlowNode parentDef(NestedClass nc) { + nc.(AnonymousClass).getClassInstanceExpr() = result or + nc.(LocalClass).getLocalClassDeclStmt() = result + } + + /** + * Gets the enclosing type of a nested class. + * + * Differs from `RefType.getEnclosingType()` by including anonymous classes defined by lambdas. + */ + private RefType desugaredGetEnclosingType(NestedClass inner) { + exists(ControlFlowNode node | + node = parentDef(inner) and + node.getEnclosingCallable().getDeclaringType() = result + ) + } + + /** + * Gets the control flow node at which the variable is read to get the value for + * a `VarAccess` inside a closure. `capturedvar` is the variable in its defining + * scope, and `closurevar` is the variable in the closure. + */ + private ControlFlowNode captureNode(TrackedVar capturedvar, TrackedVar closurevar) { + exists(LocalScopeVariable v, Callable inner, Callable outer, NestedClass innerclass, VarAccess va | + va.getVariable() = v and + inner = va.getEnclosingCallable() and + outer = v.getCallable() and + inner != outer and + inner.getDeclaringType() = innerclass and + result = parentDef(desugaredGetEnclosingType*(innerclass)) and + result.getEnclosingStmt().getEnclosingCallable() = outer and + capturedvar = TLocalVar(outer, v) and + closurevar = TLocalVar(inner, v) + ) + } + + /** Holds if `VarAccess` `use` of `v` occurs in `b` at index `i`. */ + private predicate variableUse(TrackedVar v, RValue use, BasicBlock b, int i) { + v.getAnAccess() = use and b.getNode(i) = use + } + + /** Holds if the value of `v` is captured in `b` at index `i`. */ + private predicate variableCapture(TrackedVar capturedvar, TrackedVar closurevar, BasicBlock b, int i) { + b.getNode(i) = captureNode(capturedvar, closurevar) + } + + /** Holds if the value of `v` is read in `b` at index `i`. */ + private predicate variableUseOrCapture(TrackedVar v, BasicBlock b, int i) { + variableUse(v, _, b, i) or variableCapture(v, _, b, i) + } + + /* + * Liveness analysis to restrict the size of the SSA representation. + */ + + private predicate liveAtEntry(TrackedVar v, BasicBlock b) { + exists(int i | variableUseOrCapture(v, b, i) | + not exists(int j | certainVariableUpdate(v, _, b, j) | j < i)) + or + liveAtExit(v, b) and not certainVariableUpdate(v, _, b, _) + } + private predicate liveAtExit(TrackedVar v, BasicBlock b) { + liveAtEntry(v, b.getABBSuccessor()) + } + + /* + * The SSA construction for a field `f` relies on implicit update nodes at + * every call site that conceivably could reach an update of the field. + * + * At a first approximation we need to find update paths of the form: + * Callable --(callEdge)-->* Callable(setter of f) + * + * This can be improved by excluding paths ending in: + * Constructor --(intraInstanceCallEdge)-->+ Method(setter of this.f) + * as these updates are guaranteed not to alias with the `f` under + * consideration. + * + * This set of paths can be expressed positively by noting that those + * that set `this.f` end in zero or more `intraInstanceCallEdge`s between + * methods, and before those is either the originating `Call` or a + * `crossInstanceCallEdge`. + */ + + /** + * Holds if `fw` is a field write that is not relevant as an implicit SSA + * update, since it is an initialization and therefore cannot alias. + */ + private predicate init(FieldWrite fw) { + fw.getEnclosingCallable() instanceof InitializerMethod or + fw.getEnclosingCallable() instanceof Constructor and fw.isOwnFieldAccess() or + exists(LocalVariableDecl v | + v.getAnAccess() = fw.getQualifier() and + forex(VariableAssign va | va.getDestVar() = v and exists(va.getSource()) | + va.getSource() instanceof ClassInstanceExpr + ) + ) + } + + /** + * Holds if `fw` is an update of `f` in `c` that is relevant for SSA construction. + */ + cached + predicate relevantFieldUpdate(Callable c, Field f, FieldWrite fw) { + fw = f.getAnAccess() and + not init(fw) and + fw.getEnclosingCallable() = c and + exists(TrackedField nf | nf.getField() = f) + } + + /** Holds if `c` can change the value of `this.f` and is relevant for SSA construction. */ + private predicate setsOwnField(Method c, Field f) { + exists(FieldWrite fw | relevantFieldUpdate(c, f, fw) and fw.isOwnFieldAccess()) + } + + /** + * Holds if `c` can change the value of `f` and is relevant for SSA + * construction excluding those cases covered by `setsOwnField`. + */ + private predicate setsOtherField(Callable c, Field f) { + exists(FieldWrite fw | relevantFieldUpdate(c, f, fw) and not fw.isOwnFieldAccess()) + } + + pragma[nomagic] + private predicate innerclassSupertypeStar(InnerClass t1, RefType t2) { + t1.getASupertype*().getSourceDeclaration() = t2 + } + + /** + * Holds if `(c1,m2)` is a call edge to a method that does not change the value + * of `this`. + * + * Constructor-to-constructor calls can also be intra-instance, but are not + * included, as this does not affect whether a call chain ends in + * + * ``` + * Constructor --(intraInstanceCallEdge)-->+ Method(setter of this.f) + * ``` + */ + private predicate intraInstanceCallEdge(Callable c1, Method m2) { + exists(MethodAccess ma, RefType t1 | + ma.getCaller() = c1 and + m2 = viableImpl(ma) and + not m2.isStatic() and + ( + not exists(ma.getQualifier()) or + ma.getQualifier() instanceof ThisAccess or + ma.getQualifier() instanceof SuperAccess + ) and + c1.getDeclaringType() = t1 and + if t1 instanceof InnerClass then + innerclassSupertypeStar(t1, ma.getCallee().getSourceDeclaration().getDeclaringType()) and + not exists(ma.getQualifier().(ThisAccess).getQualifier()) and + not exists(ma.getQualifier().(SuperAccess).getQualifier()) + else any() + ) + } + + private Callable tgt(Call c) { + result = viableImpl(c) or + result = getRunnerTarget(c) or + c instanceof ConstructorCall and result = c.getCallee().getSourceDeclaration() + } + + /** Holds if `(c1,c2)` is an edge in the call graph. */ + private predicate callEdge(Callable c1, Callable c2) { + exists(Call c | c.getCaller() = c1 and c2 = tgt(c)) + } + + /** Holds if `(c1,c2)` is an edge in the call graph excluding `intraInstanceCallEdge`. */ + private predicate crossInstanceCallEdge(Callable c1, Callable c2) { + callEdge(c1, c2) and not intraInstanceCallEdge(c1, c2) + } + + /** Holds if a call to `x.c` can change the value of `x.f`. The actual update occurs in `setter`. */ + private predicate setsOwnFieldTransitive(Method c, Field f, Method setter) { + setsOwnField(setter, f) and intraInstanceCallEdge*(c, setter) + } + + /** Holds if a call to `c` can change the value of `f` on some instance. The actual update occurs in `setter`. */ + private predicate generalSetter(Callable c, Field f, Callable setter) { + exists(Method ownsetter | + setsOwnFieldTransitive(ownsetter, f, setter) and + crossInstanceCallEdge(c, ownsetter) + ) + or + setsOtherField(c, f) and c = setter + } + + /** + * Holds if `call` occurs in the same basic block, `b`, as `f` at index `i` and + * `f` has an update somewhere. + */ + private predicate updateCandidate(TrackedField f, Call call, BasicBlock b, int i) { + b.getNode(i) = call and + call.getEnclosingCallable() = f.getEnclosingCallable() and + relevantFieldUpdate(_, f.getField(), _) + } + + /** + * Holds if `rankix` is the rank of index `i` at which there is a use, a + * certain update, or a potential update of `f` in the basic block `b`. + * + * Basic block indices are translated to rank indices in order to skip + * irrelevant indices at which there is update or use when traversing + * basic blocks. + */ + private predicate callDefUseRank(TrackedField f, BasicBlock b, int rankix, int i) { + updateCandidate(f, _, b, _) and + i = rank[rankix](int j | certainVariableUpdate(f, _, b, j) or variableUseOrCapture(f, b, j) or updateCandidate(f, _, b, j)) + } + + /** + * Holds if `f` is live in `b` at index `i`. The rank of `i` is `rankix` as + * defined by `callDefUseRank`. + */ + private predicate liveAtRank(TrackedField f, BasicBlock b, int rankix, int i) { + callDefUseRank(f, b, rankix, i) and + ( + rankix = max(int rix | callDefUseRank(f, b, rix, _)) and liveAtExit(f, b) + or + variableUseOrCapture(f, b, i) + or + exists(int j | liveAtRank(f, b, rankix+1, j) and not certainVariableUpdate(f, _, b, j)) + ) + } + + /** + * Holds if `call` is relevant as a potential update of `f`. This requires the + * existence of an update to `f` somewhere and that `f` is live at `call`. + */ + private predicate relevantCall(Call call, TrackedField f) { + exists(BasicBlock b, int i | + updateCandidate(f, call, b, i) and + liveAtRank(f, b, _, i) + ) + } + + /** + * Holds if `c` is a relevant part of the call graph for + * `updatesNamedFieldPart1` based on following edges in forward direction. + */ + private predicate pruneFromLeft(Callable c) { + exists(Call call, SsaSourceField f | + generalSetter(_, f.getField(), _) and + relevantCall(call, f) and + c = tgt(call) + ) + or + exists(Callable mid | pruneFromLeft(mid) and callEdge(mid, c)) + } + + /** + * Holds if `c` is a relevant part of the call graph for + * `updatesNamedFieldPart1` based on following edges in backward direction. + */ + private predicate pruneFromRight(Callable c) { + generalSetter(c, _, _) + or + exists(Callable mid | callEdge(c, mid) and pruneFromRight(mid)) + } + + /** A restriction of the call graph to the parts that are relevant for `updatesNamedFieldPart1`. */ + private class PrunedCallable extends Callable { + PrunedCallable() { + pruneFromLeft(this) and pruneFromRight(this) + } + } + + private predicate callEdgePruned(PrunedCallable c1, PrunedCallable c2) { + callEdge(c1, c2) + } + + private predicate callEdgePlus(PrunedCallable c1, PrunedCallable c2) = fastTC(callEdgePruned/2)(c1, c2) + + pragma[noinline] + private predicate updatesNamedFieldPrefix(Call call, TrackedField f, Callable c1, Field field) { + relevantCall(call, f) and + field = f.getField() and + c1 = tgt(call) + } + + pragma[noinline] + private predicate generalSetterProj(Callable c, Field f) { + generalSetter(c, f, _) + } + + /** + * Holds if `call` may change the value of `f` on some instance, which may or + * may not alias with `this`. The actual update occurs in `setter`. + */ + pragma[noopt] + private predicate updatesNamedFieldPart1(Call call, TrackedField f, Callable setter) { + exists(Callable c1, Callable c2, Field field | + updatesNamedFieldPrefix(call, f, c1, field) and + generalSetterProj(c2, field) and + (c1 = c2 or callEdgePlus(c1, c2)) and + generalSetter(c2, field, setter) + ) + } + + /** Holds if `call` may change the value of `f` on `this`. The actual update occurs in `setter`. */ + private predicate updatesNamedFieldPart2(Call call, TrackedField f, Callable setter) { + relevantCall(call, f) and + setsOwnFieldTransitive(tgt(call), f.getField(), setter) + } + + /** + * Holds if there exists a call-chain originating in `call` that can update `f` on some instance + * where `f` and `call` share the same enclosing callable in which a + * `FieldRead` of `f` is reachable from `call`. + */ + cached + predicate updatesNamedField(Call call, TrackedField f, Callable setter) { + updatesNamedFieldPart1(call, f, setter) or updatesNamedFieldPart2(call, f, setter) + } + + /** Holds if `n` might update the locally tracked variable `v`. */ + cached + predicate uncertainVariableUpdate(TrackedVar v, ControlFlowNode n, BasicBlock b, int i) { + exists(Call c | c = n | updatesNamedField(c, v, _)) and b.getNode(i) = n or + uncertainVariableUpdate(v.getQualifier(), n, b, i) + } + + /** Holds if `n` updates the locally tracked variable `v`. */ + private predicate variableUpdate(TrackedVar v, ControlFlowNode n, BasicBlock b, int i) { + certainVariableUpdate(v, n, b, i) or uncertainVariableUpdate(v, n, b, i) + } + + /** Holds if a phi node for `v` is needed at the beginning of basic block `b`. */ + cached + predicate phiNode(TrackedVar v, BasicBlock b) { + liveAtEntry(v, b) and + exists(BasicBlock def | dominanceFrontier(def, b) | + variableUpdate(v, _, def, _) or phiNode(v, def) + ) + } + + /** Holds if `v` has an implicit definition at the entry, `b`, of the callable. */ + cached + predicate hasEntryDef(TrackedVar v, BasicBlock b) { + exists(LocalScopeVariable l, Callable c | + v = TLocalVar(c, l) and c.getBody() = b + | + l instanceof Parameter or + l.getCallable() != c + ) or + v instanceof SsaSourceField and v.getEnclosingCallable().getBody() = b and liveAtEntry(v, b) + } + + /** + * The construction of SSA form ensures that each use of a variable is + * dominated by its definition. A definition of an SSA variable therefore + * reaches a `ControlFlowNode` if it is the _closest_ SSA variable definition + * that dominates the node. If two definitions dominate a node then one must + * dominate the other, so therefore the definition of _closest_ is given by the + * dominator tree. Thus, reaching definitions can be calculated in terms of + * dominance. + */ + cached module SsaDefReaches { + + /** + * Holds if `rankix` is the rank the index `i` at which there is an SSA definition or use of + * `v` in the basic block `b`. + * + * Basic block indices are translated to rank indices in order to skip + * irrelevant indices at which there is no definition or use when traversing + * basic blocks. + */ + private predicate defUseRank(TrackedVar v, BasicBlock b, int rankix, int i) { + i = rank[rankix](int j | any(TrackedSsaDef def).definesAt(v, b, j) or variableUseOrCapture(v, b, j)) + } + + /** Gets the maximum rank index for the given variable and basic block. */ + private int lastRank(TrackedVar v, BasicBlock b) { + result = max(int rankix | defUseRank(v, b, rankix, _)) + } + + /** Holds if a definition of an SSA variable occurs at the specified rank index in basic block `b`. */ + private predicate ssaDefRank(TrackedVar v, TrackedSsaDef def, BasicBlock b, int rankix) { + exists(int i | + def.definesAt(v, b, i) and + defUseRank(v, b, rankix, i) + ) + } + + /** Holds if the SSA definition reaches the rank index `rankix` in its own basic block `b`. */ + private predicate ssaDefReachesRank(TrackedVar v, TrackedSsaDef def, BasicBlock b, int rankix) { + ssaDefRank(v, def, b, rankix) or + ssaDefReachesRank(v, def, b, rankix-1) and rankix <= lastRank(v, b) and not ssaDefRank(v, _, b, rankix) + } + + /** + * Holds if the SSA definition of `v` at `def` reaches the end of a basic block `b`, at + * which point it is still live, without crossing another SSA definition of `v`. + */ + cached + predicate ssaDefReachesEndOfBlock(TrackedVar v, TrackedSsaDef def, BasicBlock b) { + liveAtExit(v, b) and + ( + ssaDefReachesRank(v, def, b, lastRank(v, b)) or + exists(BasicBlock idom | + bbIDominates(idom, b) and // It is sufficient to traverse the dominator graph, cf. discussion above. + ssaDefReachesEndOfBlock(v, def, idom) and + not any(TrackedSsaDef other).definesAt(v, b, _) + ) + ) + } + + /** + * Holds if the SSA definition of `v` at `def` reaches `use` in the same basic block + * without crossing another SSA definition of `v`. + */ + private predicate ssaDefReachesUseWithinBlock(TrackedVar v, TrackedSsaDef def, RValue use) { + exists(BasicBlock b, int rankix, int i | + ssaDefReachesRank(v, def, b, rankix) and + defUseRank(v, b, rankix, i) and + variableUse(v, use, b, i) + ) + } + + /** + * Holds if the SSA definition of `v` at `def` reaches `use` without crossing another + * SSA definition of `v`. + */ + cached + predicate ssaDefReachesUse(TrackedVar v, TrackedSsaDef def, RValue use) { + ssaDefReachesUseWithinBlock(v, def, use) or + exists(BasicBlock b | + variableUse(v, use, b, _) and + ssaDefReachesEndOfBlock(v, def, b.getABBPredecessor()) and + not ssaDefReachesUseWithinBlock(v, _, use) + ) + } + + /** + * Holds if the SSA definition of `v` at `def` reaches the capture point of + * `closurevar` in the same basic block without crossing another SSA + * definition of `v`. + */ + private predicate ssaDefReachesCaptureWithinBlock(TrackedVar v, TrackedSsaDef def, TrackedVar closurevar) { + exists(BasicBlock b, int rankix, int i | + ssaDefReachesRank(v, def, b, rankix) and + defUseRank(v, b, rankix, i) and + variableCapture(v, closurevar, b, i) + ) + } + + /** + * Holds if the SSA definition of `v` at `def` reaches capture point of + * `closurevar` without crossing another SSA definition of `v`. + */ + cached + predicate ssaDefReachesCapture(TrackedVar v, TrackedSsaDef def, TrackedVar closurevar) { + ssaDefReachesCaptureWithinBlock(v, def, closurevar) or + exists(BasicBlock b | + variableCapture(v, closurevar, b, _) and + ssaDefReachesEndOfBlock(v, def, b.getABBPredecessor()) and + not ssaDefReachesCaptureWithinBlock(v, _, closurevar) + ) + } + + /** + * Holds if the SSA definition of `v` at `def` reaches `redef` in the same basic block + * without crossing another SSA definition of `v`. + */ + private predicate ssaDefReachesUncertainDefWithinBlock(TrackedVar v, TrackedSsaDef def, SsaUncertainImplicitUpdate redef) { + exists(BasicBlock b, int rankix, int i | + ssaDefReachesRank(v, def, b, rankix) and + defUseRank(v, b, rankix+1, i) and + redef.(TrackedSsaDef).definesAt(v, b, i) + ) + } + + /** + * Holds if the SSA definition of `v` at `def` reaches `redef` without crossing another + * SSA definition of `v`. + */ + cached + predicate ssaDefReachesUncertainDef(TrackedVar v, TrackedSsaDef def, SsaUncertainImplicitUpdate redef) { + ssaDefReachesUncertainDefWithinBlock(v, def, redef) or + exists(BasicBlock b | + redef.(TrackedSsaDef).definesAt(v, b, _) and + ssaDefReachesEndOfBlock(v, def, b.getABBPredecessor()) and + not ssaDefReachesUncertainDefWithinBlock(v, _, redef) + ) + } + + } + + private module AdjacentUsesImpl { + + /** + * Holds if `rankix` is the rank the index `i` at which there is an SSA definition or explicit use of + * `v` in the basic block `b`. + */ + private predicate defUseRank(TrackedVar v, BasicBlock b, int rankix, int i) { + i = rank[rankix](int j | any(TrackedSsaDef def).definesAt(v, b, j) or variableUse(v, _, b, j)) + } + + /** Gets the maximum rank index for the given variable and basic block. */ + private int lastRank(TrackedVar v, BasicBlock b) { + result = max(int rankix | defUseRank(v, b, rankix, _)) + } + + /** Holds if `v` is defined or used in `b`. */ + private predicate varOccursInBlock(TrackedVar v, BasicBlock b) { + defUseRank(v, b, _, _) + } + + /** Holds if `v` occurs in `b` or one of `b`'s transitive successors. */ + private predicate blockPrecedesVar(TrackedVar v, BasicBlock b) { + varOccursInBlock(v, b.getABBSuccessor*()) + } + + /** + * Holds if `b2` is a transitive successor of `b1` and `v` occurs in `b1` and + * in `b2` or one of its transitive successors but not in any block on the path + * between `b1` and `b2`. + */ + private predicate varBlockReaches(TrackedVar v, BasicBlock b1, BasicBlock b2) { + varOccursInBlock(v, b1) and b2 = b1.getABBSuccessor() or + exists(BasicBlock mid | + varBlockReaches(v, b1, mid) and + b2 = mid.getABBSuccessor() and + not varOccursInBlock(v, mid) and + blockPrecedesVar(v, b2) + ) + } + + /** + * Holds if `b2` is a transitive successor of `b1` and `v` occurs in `b1` and + * `b2` but not in any block on the path between `b1` and `b2`. + */ + private predicate varBlockStep(TrackedVar v, BasicBlock b1, BasicBlock b2) { + varBlockReaches(v, b1, b2) and + varOccursInBlock(v, b2) + } + + /** + * Holds if `v` occurs at index `i1` in `b1` and at index `i2` in `b2` and + * there is a path between them without any occurrence of `v`. + */ + predicate adjacentVarRefs(TrackedVar v, BasicBlock b1, int i1, BasicBlock b2, int i2) { + exists(int rankix | + b1 = b2 and + defUseRank(v, b1, rankix, i1) and + defUseRank(v, b2, rankix+1, i2) + ) or + defUseRank(v, b1, lastRank(v, b1), i1) and + varBlockStep(v, b1, b2) and + defUseRank(v, b2, 1, i2) + } + + } + private import AdjacentUsesImpl + + /** + * Holds if the value defined at `def` can reach `use` without passing through + * any other uses, but possibly through phi nodes and uncertain implicit updates. + */ + cached + predicate firstUse(TrackedSsaDef def, RValue use) { + exists(TrackedVar v, BasicBlock b1, int i1, BasicBlock b2, int i2 | + adjacentVarRefs(v, b1, i1, b2, i2) and + def.definesAt(v, b1, i1) and + variableUse(v, use, b2, i2) + ) or + exists(TrackedVar v, TrackedSsaDef redef, BasicBlock b1, int i1, BasicBlock b2, int i2 | + redef instanceof SsaUncertainImplicitUpdate or redef instanceof SsaPhiNode + | + adjacentVarRefs(v, b1, i1, b2, i2) and + def.definesAt(v, b1, i1) and + redef.definesAt(v, b2, i2) and + firstUse(redef, use) + ) + } + + cached module SsaPublic { + + /** + * Holds if `use1` and `use2` form an adjacent use-use-pair of the same SSA + * variable, that is, the value read in `use1` can reach `use2` without passing + * through any other use or any SSA definition of the variable. + */ + cached + predicate adjacentUseUseSameVar(RValue use1, RValue use2) { + exists(TrackedVar v, BasicBlock b1, int i1, BasicBlock b2, int i2 | + adjacentVarRefs(v, b1, i1, b2, i2) and + variableUse(v, use1, b1, i1) and + variableUse(v, use2, b2, i2) + ) + } + + /** + * Holds if `use1` and `use2` form an adjacent use-use-pair of the same + * `SsaSourceVariable`, that is, the value read in `use1` can reach `use2` + * without passing through any other use or any SSA definition of the variable + * except for phi nodes and uncertain implicit updates. + */ + cached + predicate adjacentUseUse(RValue use1, RValue use2) { + adjacentUseUseSameVar(use1, use2) or + exists(TrackedVar v, TrackedSsaDef def, BasicBlock b1, int i1, BasicBlock b2, int i2 | + adjacentVarRefs(v, b1, i1, b2, i2) and + variableUse(v, use1, b1, i1) and + def.definesAt(v, b2, i2) and + firstUse(def, use2) and + (def instanceof SsaUncertainImplicitUpdate or def instanceof SsaPhiNode) + ) + } + + } + +} +private import SsaImpl +private import SsaDefReaches +import SsaPublic + +cached +private newtype TSsaVariable = + TSsaPhiNode(TrackedVar v, BasicBlock b) { phiNode(v, b) } or + TSsaCertainUpdate(TrackedVar v, ControlFlowNode n, BasicBlock b, int i) { certainVariableUpdate(v, n, b, i) } or + TSsaUncertainUpdate(TrackedVar v, ControlFlowNode n, BasicBlock b, int i) { uncertainVariableUpdate(v, n, b, i) } or + TSsaEntryDef(TrackedVar v, BasicBlock b) { hasEntryDef(v, b) } or + TSsaUntracked(SsaSourceField nf, ControlFlowNode n) { + n = nf.getAnAccess().(FieldRead) and not trackField(nf) + } + +/** + * An SSA definition excluding those variables that use a trivial SSA construction. + */ +private class TrackedSsaDef extends SsaVariable { + TrackedSsaDef() { + not this = TSsaUntracked(_, _) + } + + /** + * Holds if this SSA definition occurs at the specified position. + * Phi nodes are placed at index -1. + */ + predicate definesAt(TrackedVar v, BasicBlock b, int i) { + this = TSsaPhiNode(v, b) and i = -1 or + this = TSsaCertainUpdate(v, _, b, i) or + this = TSsaUncertainUpdate(v, _, b, i) or + this = TSsaEntryDef(v, b) and i = 0 + } +} + +/** + * An SSA variable. + */ +class SsaVariable extends TSsaVariable { + /** Gets the SSA source variable underlying this SSA variable. */ + SsaSourceVariable getSourceVariable() { + this = TSsaPhiNode(result, _) or + this = TSsaCertainUpdate(result, _, _, _) or + this = TSsaUncertainUpdate(result, _, _, _) or + this = TSsaEntryDef(result, _) or + this = TSsaUntracked(result, _) + } + + /** Gets the `ControlFlowNode` at which this SSA variable is defined. */ + ControlFlowNode getCFGNode() { + this = TSsaPhiNode(_, result) or + this = TSsaCertainUpdate(_, result, _, _) or + this = TSsaUncertainUpdate(_, result, _, _) or + this = TSsaEntryDef(_, result) or + this = TSsaUntracked(_, result) + } + + string toString() { none() } + + Location getLocation() { result = getCFGNode().getLocation() } + + /** Gets the `BasicBlock` in which this SSA variable is defined. */ + BasicBlock getBasicBlock() { result = getCFGNode().getBasicBlock() } + + /** Gets an access of this SSA variable. */ + RValue getAUse() { + ssaDefReachesUse(_, this, result) or + this = TSsaUntracked(_, result) + } + + /** + * Gets an access of the SSA source variable underlying this SSA variable + * that can be reached from this SSA variable without passing through any + * other uses, but potentially through phi nodes and uncertain implicit + * updates. + * + * Subsequent uses can be found by following the steps defined by + * `adjacentUseUse`. + */ + RValue getAFirstUse() { + firstUse(this, result) or + this = TSsaUntracked(_, result) + } + + /** Holds if this SSA variable is live at the end of `b`. */ + predicate isLiveAtEndOfBlock(BasicBlock b) { + ssaDefReachesEndOfBlock(_, this, b) + } + + /** + * Gets an SSA variable whose value can flow to this one in one step. This + * includes inputs to phi nodes, the prior definition of uncertain updates, + * and the captured ssa variable for a closure variable. + */ + private SsaVariable getAPhiInputOrPriorDef() { + result = this.(SsaPhiNode).getAPhiInput() or + result = this.(SsaUncertainImplicitUpdate).getPriorDef() or + this.(SsaImplicitInit).captures(result) + } + + /** Gets a definition that ultimately defines this variable and is not itself a phi node. */ + SsaVariable getAnUltimateDefinition() { + result = this.getAPhiInputOrPriorDef*() and not result instanceof SsaPhiNode + } +} + +/** An SSA variable that either explicitly or implicitly updates the variable. */ +class SsaUpdate extends SsaVariable { + SsaUpdate() { + this = TSsaCertainUpdate(_, _, _, _) or + this = TSsaUncertainUpdate(_, _, _, _) or + this = TSsaUntracked(_, _) + } +} + +/** An SSA variable that is defined by a `VariableUpdate`. */ +class SsaExplicitUpdate extends SsaUpdate, TSsaCertainUpdate { + SsaExplicitUpdate() { + exists(VariableUpdate upd | + upd = this.getCFGNode() and getDestVar(upd) = getSourceVariable() + ) + } + + override string toString() { + result = "SSA def(" + getSourceVariable() + ")" + } + + /** Gets the `VariableUpdate` defining the SSA variable. */ + VariableUpdate getDefiningExpr() { + result = this.getCFGNode() and getDestVar(result) = getSourceVariable() + } +} + +/** + * An SSA variable that represents any sort of implicit update. This can be a + * `Call` that might reach a non-local update of the field, an explicit or + * implicit update of the qualifier of the field, or the implicit update that + * occurs just prior to a `FieldRead` of an untracked field. + */ +class SsaImplicitUpdate extends SsaUpdate { + SsaImplicitUpdate() { + not this instanceof SsaExplicitUpdate + } + + override string toString() { + result = "SSA impl upd[" + getKind() + "](" + getSourceVariable() + ")" + } + + private string getKind() { + this = TSsaUntracked(_, _) and result = "untracked" or + certainVariableUpdate(getSourceVariable().getQualifier(), getCFGNode(), _, _) and result = "explicit qualifier" or + if uncertainVariableUpdate(getSourceVariable().getQualifier(), getCFGNode(), _, _) then + if exists(getANonLocalUpdate()) then + result = "nonlocal + nonlocal qualifier" + else + result = "nonlocal qualifier" + else + (exists(getANonLocalUpdate()) and result = "nonlocal") + } + + /** + * Gets a reachable `FieldWrite` that might represent this ssa update, if any. + */ + FieldWrite getANonLocalUpdate() { + exists(SsaSourceField f, Callable setter | + f = getSourceVariable() and + relevantFieldUpdate(setter, f.getField(), result) and + updatesNamedField(getCFGNode(), f, setter) + ) + } + + /** + * Holds if this ssa variable might change the value to something unknown. + * + * Examples include updates that might change the value of the qualifier, or + * reads from untracked variables, for example those where the field or one + * of its qualifiers is volatile. + */ + predicate assignsUnknownValue() { + this = TSsaUntracked(_, _) or + certainVariableUpdate(getSourceVariable().getQualifier(), getCFGNode(), _, _) or + uncertainVariableUpdate(getSourceVariable().getQualifier(), getCFGNode(), _, _) + } +} + +/** + * An SSA variable that represents an uncertain implicit update of the value. + * This is a `Call` that might reach a non-local update of the field or one of + * its qualifiers. + */ +class SsaUncertainImplicitUpdate extends SsaImplicitUpdate, TSsaUncertainUpdate { + /** + * Gets the immediately preceding definition. Since this update is uncertain + * the value from the preceding definition might still be valid. + */ + SsaVariable getPriorDef() { + ssaDefReachesUncertainDef(_, result, this) + } +} + +/** + * An SSA variable that is defined by its initial value in the callable. This + * includes initial values of parameters, fields, and closure variables. + */ +class SsaImplicitInit extends SsaVariable, TSsaEntryDef { + override string toString() { + result = "SSA init(" + getSourceVariable() + ")" + } + + /** Holds if this is a closure variable that captures the value of `capturedvar`. */ + predicate captures(SsaVariable capturedvar) { + ssaDefReachesCapture(_, capturedvar, getSourceVariable()) + } + + /** + * Holds if the SSA variable is a parameter defined by its initial value in the callable. + */ + predicate isParameterDefinition(Parameter p) { + getSourceVariable() = TLocalVar(p.getCallable(), p) and p.getCallable().getBody() = getCFGNode() + } +} + +/** An SSA phi node. */ +class SsaPhiNode extends SsaVariable, TSsaPhiNode { + override string toString() { + result = "SSA phi(" + getSourceVariable() + ")" + } + + /** Gets an input to the phi node defining the SSA variable. */ + SsaVariable getAPhiInput() { + exists(BasicBlock phiPred, TrackedVar v | + v = getSourceVariable() and + getCFGNode().(BasicBlock).getABBPredecessor() = phiPred and + ssaDefReachesEndOfBlock(v, result, phiPred) + ) + } + + /** Holds if `inp` is an input to the phi node along the edge originating in `bb`. */ + predicate hasInputFromBlock(SsaVariable inp, BasicBlock bb) { + this.getAPhiInput() = inp and + this.getBasicBlock().getABBPredecessor() = bb and + inp.isLiveAtEndOfBlock(bb) + } +} + +library class RefTypeCastExpr extends CastExpr { + RefTypeCastExpr() { this.getType() instanceof RefType } +} + +/** + * Gets an expression that has the same value as the given SSA variable. + * + * The `VarAccess` represents the access to `v` that `result` has the same value as. + */ +Expr sameValue(SsaVariable v, VarAccess va) { + result = v.getAUse() and result = va or + result.(AssignExpr).getDest() = va and result = v.(SsaExplicitUpdate).getDefiningExpr() or + result.(AssignExpr).getSource() = sameValue(v, va) or + result.(ParExpr).getExpr() = sameValue(v, va) or + result.(RefTypeCastExpr).getExpr() = sameValue(v, va) +} diff --git a/java/ql/src/semmle/code/java/dataflow/SignAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/SignAnalysis.qll new file mode 100644 index 00000000000..aa4eb8bb647 --- /dev/null +++ b/java/ql/src/semmle/code/java/dataflow/SignAnalysis.qll @@ -0,0 +1,447 @@ +/** + * Provides sign analysis to determine whether expression are always positive + * or negative. + * + * The analysis is implemented as an abstract interpretation over the + * three-valued domain `{negative, zero, positive}`. + */ +import java +private import SSA +private import RangeUtils +private import semmle.code.java.controlflow.Guards +private import semmle.code.java.Reflection +private import semmle.code.java.Collections +private import semmle.code.java.Maps + +private newtype TSign = TNeg() or TZero() or TPos() +private class Sign extends TSign { + string toString() { + result = "-" and this = TNeg() or + result = "0" and this = TZero() or + result = "+" and this = TPos() + } + Sign inc() { + this = TNeg() and result = TNeg() or + this = TNeg() and result = TZero() or + this = TZero() and result = TPos() or + this = TPos() and result = TPos() + } + Sign dec() { + result.inc() = this + } + Sign neg() { + this = TNeg() and result = TPos() or + this = TZero() and result = TZero() or + this = TPos() and result = TNeg() + } + Sign bitnot() { + this = TNeg() and result = TPos() or + this = TNeg() and result = TZero() or + this = TZero() and result = TNeg() or + this = TPos() and result = TNeg() + } + Sign add(Sign s) { + this = TZero() and result = s or + s = TZero() and result = this or + this = s and this = result or + this = TPos() and s = TNeg() or + this = TNeg() and s = TPos() + } + Sign mul(Sign s) { + result = TZero() and this = TZero() or + result = TZero() and s = TZero() or + result = TNeg() and this = TPos() and s = TNeg() or + result = TNeg() and this = TNeg() and s = TPos() or + result = TPos() and this = TPos() and s = TPos() or + result = TPos() and this = TNeg() and s = TNeg() + } + Sign div(Sign s) { + result = TZero() and s = TNeg() or + result = TZero() and s = TPos() or + result = TNeg() and this = TPos() and s = TNeg() or + result = TNeg() and this = TNeg() and s = TPos() or + result = TPos() and this = TPos() and s = TPos() or + result = TPos() and this = TNeg() and s = TNeg() + } + Sign rem(Sign s) { + result = TZero() and s = TNeg() or + result = TZero() and s = TPos() or + result = this and s = TNeg() or + result = this and s = TPos() + } + Sign bitand(Sign s) { + result = TZero() and this = TZero() or + result = TZero() and s = TZero() or + result = TZero() and this = TPos() or + result = TZero() and s = TPos() or + result = TNeg() and this = TNeg() and s = TNeg() or + result = TPos() and this = TNeg() and s = TPos() or + result = TPos() and this = TPos() and s = TNeg() or + result = TPos() and this = TPos() and s = TPos() + } + Sign bitor(Sign s) { + result = TZero() and this = TZero() and s = TZero() or + result = TNeg() and this = TNeg() or + result = TNeg() and s = TNeg() or + result = TPos() and this = TPos() and s = TZero() or + result = TPos() and this = TZero() and s = TPos() or + result = TPos() and this = TPos() and s = TPos() + } + Sign bitxor(Sign s) { + result = TZero() and this = s or + result = this and s = TZero() or + result = s and this = TZero() or + result = TPos() and this = TPos() and s = TPos() or + result = TNeg() and this = TNeg() and s = TPos() or + result = TNeg() and this = TPos() and s = TNeg() or + result = TPos() and this = TNeg() and s = TNeg() + } + Sign lshift(Sign s) { + result = TZero() and this = TZero() or + result = this and s = TZero() or + this != TZero() and s != TZero() + } + Sign rshift(Sign s) { + result = TZero() and this = TZero() or + result = this and s = TZero() or + result = TNeg() and this = TNeg() or + result != TNeg() and this = TPos() and s != TZero() + } + Sign urshift(Sign s) { + result = TZero() and this = TZero() or + result = this and s = TZero() or + result != TZero() and this = TNeg() and s != TZero() or + result != TNeg() and this = TPos() and s != TZero() + } +} + +/** Gets the sign of `e` if this can be directly determined. */ +private Sign certainExprSign(Expr e) { + exists(int i | e.(ConstantIntegerExpr).getIntValue() = i | + i < 0 and result = TNeg() or + i = 0 and result = TZero() or + i > 0 and result = TPos() + ) or + not exists(e.(ConstantIntegerExpr).getIntValue()) and + ( + exists(float f | + f = e.(LongLiteral).getValue().toFloat() or + f = e.(FloatingPointLiteral).getValue().toFloat() or + f = e.(DoubleLiteral).getValue().toFloat() + | + f < 0 and result = TNeg() or + f = 0 and result = TZero() or + f > 0 and result = TPos() + ) or + exists(string charlit | charlit = e.(CharacterLiteral).getValue() | + if charlit = "\\0" or charlit = "\\u0000" then + result = TZero() + else + result = TPos() + ) or + e.(MethodAccess).getMethod() instanceof StringLengthMethod and (result = TPos() or result = TZero()) or + e.(MethodAccess).getMethod() instanceof CollectionSizeMethod and (result = TPos() or result = TZero()) or + e.(MethodAccess).getMethod() instanceof MapSizeMethod and (result = TPos() or result = TZero()) + ) +} + +/** Holds if the sign of `e` is too complicated to determine. */ +private predicate unknownSign(Expr e) { + not exists(e.(ConstantIntegerExpr).getIntValue()) and + ( + exists(IntegerLiteral lit | lit = e and not exists(lit.getValue().toInt())) or + exists(LongLiteral lit | lit = e and not exists(lit.getValue().toFloat())) or + exists(CastExpr cast, Type fromtyp | + cast = e and + fromtyp = cast.getExpr().getType() and + not fromtyp instanceof NumericOrCharType + ) or + e instanceof ArrayAccess and e.getType() instanceof NumericOrCharType or + e instanceof MethodAccess and e.getType() instanceof NumericOrCharType or + e instanceof ClassInstanceExpr and e.getType() instanceof NumericOrCharType + ) +} + +/** + * Holds if `lowerbound` is a lower bound for `v` at `pos`. This is restricted + * to only include bounds for which we might determine a sign. + */ +private predicate lowerBound(Expr lowerbound, SsaVariable v, SsaReadPosition pos, boolean isStrict) { + exists(boolean testIsTrue, ComparisonExpr comp | + pos.hasReadOfVar(v) and + guardControlsSsaRead(comp, pos, testIsTrue) and + not unknownSign(lowerbound) + | + testIsTrue = true and + comp.getLesserOperand() = lowerbound and + comp.getGreaterOperand() = ssaRead(v, 0) and + (if comp.isStrict() then isStrict = true else isStrict = false) + or + testIsTrue = false and + comp.getGreaterOperand() = lowerbound and + comp.getLesserOperand() = ssaRead(v, 0) and + (if comp.isStrict() then isStrict = false else isStrict = true) + ) +} + +/** + * Holds if `upperbound` is an upper bound for `v` at `pos`. This is restricted + * to only include bounds for which we might determine a sign. + */ +private predicate upperBound(Expr upperbound, SsaVariable v, SsaReadPosition pos, boolean isStrict) { + exists(boolean testIsTrue, ComparisonExpr comp | + pos.hasReadOfVar(v) and + guardControlsSsaRead(comp, pos, testIsTrue) and + not unknownSign(upperbound) + | + testIsTrue = true and + comp.getGreaterOperand() = upperbound and + comp.getLesserOperand() = ssaRead(v, 0) and + (if comp.isStrict() then isStrict = true else isStrict = false) + or + testIsTrue = false and + comp.getLesserOperand() = upperbound and + comp.getGreaterOperand() = ssaRead(v, 0) and + (if comp.isStrict() then isStrict = false else isStrict = true) + ) +} + +/** + * Holds if `eqbound` is an equality/inequality for `v` at `pos`. This is + * restricted to only include bounds for which we might determine a sign. The + * boolean `isEq` gives the polarity: + * - `isEq = true` : `v = eqbound` + * - `isEq = false` : `v != eqbound` + */ +private predicate eqBound(Expr eqbound, SsaVariable v, SsaReadPosition pos, boolean isEq) { + exists(Guard guard, boolean testIsTrue, boolean polarity | + pos.hasReadOfVar(v) and + guardControlsSsaRead(guard, pos, testIsTrue) and + guard.isEquality(eqbound, ssaRead(v, 0), polarity) and + isEq = polarity.booleanXor(testIsTrue).booleanNot() and + not unknownSign(eqbound) + ) +} + +/** + * Holds if `bound` is a bound for `v` at `pos` that needs to be positive in + * order for `v` to be positive. + */ +private predicate posBound(Expr bound, SsaVariable v, SsaReadPosition pos) { + upperBound(bound, v, pos, _) or + eqBound(bound, v, pos, true) +} + +/** + * Holds if `bound` is a bound for `v` at `pos` that needs to be negative in + * order for `v` to be negative. + */ +private predicate negBound(Expr bound, SsaVariable v, SsaReadPosition pos) { + lowerBound(bound, v, pos, _) or + eqBound(bound, v, pos, true) +} + +/** + * Holds if `bound` is a bound for `v` at `pos` that can restrict whether `v` + * can be zero. + */ +private predicate zeroBound(Expr bound, SsaVariable v, SsaReadPosition pos) { + lowerBound(bound, v, pos, _) or + upperBound(bound, v, pos, _) or + eqBound(bound, v, pos, _) +} + +/** Holds if `bound` allows `v` to be positive at `pos`. */ +private predicate posBoundOk(Expr bound, SsaVariable v, SsaReadPosition pos) { + posBound(bound, v, pos) and TPos() = exprSign(bound) +} + +/** Holds if `bound` allows `v` to be negative at `pos`. */ +private predicate negBoundOk(Expr bound, SsaVariable v, SsaReadPosition pos) { + negBound(bound, v, pos) and TNeg() = exprSign(bound) +} + +/** Holds if `bound` allows `v` to be zero at `pos`. */ +private predicate zeroBoundOk(Expr bound, SsaVariable v, SsaReadPosition pos) { + lowerBound(bound, v, pos, _) and TNeg() = exprSign(bound) or + lowerBound(bound, v, pos, false) and TZero() = exprSign(bound) or + upperBound(bound, v, pos, _) and TPos() = exprSign(bound) or + upperBound(bound, v, pos, false) and TZero() = exprSign(bound) or + eqBound(bound, v, pos, true) and TZero() = exprSign(bound) or + eqBound(bound, v, pos, false) and TZero() != exprSign(bound) +} + +/** + * Holds if there is a bound that might restrict whether `v` has the sign `s` + * at `pos`. + */ +private predicate hasGuard(SsaVariable v, SsaReadPosition pos, Sign s) { + s = TPos() and posBound(_, v, pos) or + s = TNeg() and negBound(_, v, pos) or + s = TZero() and zeroBound(_, v, pos) +} + +pragma[noinline] +private Sign guardedSsaSign(SsaVariable v, SsaReadPosition pos) { + result = ssaDefSign(v) and + pos.hasReadOfVar(v) and + hasGuard(v, pos, result) +} + +pragma[noinline] +private Sign unguardedSsaSign(SsaVariable v, SsaReadPosition pos) { + result = ssaDefSign(v) and + pos.hasReadOfVar(v) and + not hasGuard(v, pos, result) +} + +private Sign guardedSsaSignOk(SsaVariable v, SsaReadPosition pos) { + result = TPos() and forex(Expr bound | posBound(bound, v, pos) | posBoundOk(bound, v, pos)) or + result = TNeg() and forex(Expr bound | negBound(bound, v, pos) | negBoundOk(bound, v, pos)) or + result = TZero() and forex(Expr bound | zeroBound(bound, v, pos) | zeroBoundOk(bound, v, pos)) +} + +/** Gets a possible sign for `v` at `pos`. */ +private Sign ssaSign(SsaVariable v, SsaReadPosition pos) { + result = unguardedSsaSign(v, pos) + or + result = guardedSsaSign(v, pos) and + result = guardedSsaSignOk(v, pos) +} + +/** Gets a possible sign for `v`. */ +pragma[nomagic] +private Sign ssaDefSign(SsaVariable v) { + exists(VariableUpdate def | def = v.(SsaExplicitUpdate).getDefiningExpr() | + result = exprSign(def.(VariableAssign).getSource()) or + exists(EnhancedForStmt for | def = for.getVariable()) or + result = exprSign(def.(PostIncExpr).getExpr()).inc() or + result = exprSign(def.(PreIncExpr).getExpr()).inc() or + result = exprSign(def.(PostDecExpr).getExpr()).dec() or + result = exprSign(def.(PreDecExpr).getExpr()).dec() or + exists(AssignOp a | a = def and result = exprSign(a)) + ) or + result = fieldSign(v.(SsaImplicitUpdate).getSourceVariable().getVariable()) or + result = fieldSign(v.(SsaImplicitInit).getSourceVariable().getVariable()) or + exists(Parameter p | v.(SsaImplicitInit).isParameterDefinition(p)) or + exists(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge | + v = phi and + edge.phiInput(phi, inp) and + result = ssaSign(inp, edge) + ) +} + +/** Gets a possible sign for `f`. */ +private Sign fieldSign(Field f) { + result = exprSign(f.getAnAssignedValue()) or + exists(PostIncExpr inc | inc.getExpr() = f.getAnAccess() and result = fieldSign(f).inc()) or + exists(PreIncExpr inc | inc.getExpr() = f.getAnAccess() and result = fieldSign(f).inc()) or + exists(PostDecExpr inc | inc.getExpr() = f.getAnAccess() and result = fieldSign(f).dec()) or + exists(PreDecExpr inc | inc.getExpr() = f.getAnAccess() and result = fieldSign(f).dec()) or + exists(AssignOp a | a.getDest() = f.getAnAccess() | result = exprSign(a)) or + exists(ReflectiveFieldAccess rfa | rfa.inferAccessedField() = f) + or + if f.fromSource() then + not exists(f.getInitializer()) and result = TZero() + else if f instanceof ArrayLengthField then result != TNeg() + else if f.hasName("MAX_VALUE") then result = TPos() + else if f.hasName("MIN_VALUE") then result = TNeg() + else any() +} + +/** Gets a possible sign for `e`. */ +cached +private Sign exprSign(Expr e) { + result = certainExprSign(e) or + not exists(certainExprSign(e)) and + ( + unknownSign(e) or + result = exprSign(e.(ParExpr).getExpr()) or + exists(SsaVariable v | v.getAUse() = e | + result = ssaSign(v, any(SsaReadPositionBlock bb | bb.getBlock() = e.getBasicBlock())) or + not exists(e.getBasicBlock()) and result = ssaDefSign(v) + ) or + exists(FieldAccess fa | fa = e | + not exists(SsaVariable v | v.getAUse() = fa) and + result = fieldSign(fa.getField()) + ) or + exists(VarAccess va | va = e | + not exists(SsaVariable v | v.getAUse() = va) and + not va instanceof FieldAccess + ) or + result = exprSign(e.(AssignExpr).getSource()) or + result = exprSign(e.(PlusExpr).getExpr()) or + result = exprSign(e.(PostIncExpr).getExpr()) or + result = exprSign(e.(PostDecExpr).getExpr()) or + result = exprSign(e.(PreIncExpr).getExpr()).inc() or + result = exprSign(e.(PreDecExpr).getExpr()).dec() or + result = exprSign(e.(MinusExpr).getExpr()).neg() or + result = exprSign(e.(BitNotExpr).getExpr()).bitnot() or + exists(DivExpr div | + div = e and + result = exprSign(div.getLeftOperand()) and + result != TZero() + | + div.getRightOperand().(FloatingPointLiteral).getValue().toFloat() = 0 or + div.getRightOperand().(DoubleLiteral).getValue().toFloat() = 0 + ) or + exists(Sign s1, Sign s2 | + binaryOpSigns(e, s1, s2) + | + (e instanceof AssignAddExpr or e instanceof AddExpr) and result = s1.add(s2) or + (e instanceof AssignSubExpr or e instanceof SubExpr) and result = s1.add(s2.neg()) or + (e instanceof AssignMulExpr or e instanceof MulExpr) and result = s1.mul(s2) or + (e instanceof AssignDivExpr or e instanceof DivExpr) and result = s1.div(s2) or + (e instanceof AssignRemExpr or e instanceof RemExpr) and result = s1.rem(s2) or + (e instanceof AssignAndExpr or e instanceof AndBitwiseExpr) and result = s1.bitand(s2) or + (e instanceof AssignOrExpr or e instanceof OrBitwiseExpr) and result = s1.bitor(s2) or + (e instanceof AssignXorExpr or e instanceof XorBitwiseExpr) and result = s1.bitxor(s2) or + (e instanceof AssignLShiftExpr or e instanceof LShiftExpr) and result = s1.lshift(s2) or + (e instanceof AssignRShiftExpr or e instanceof RShiftExpr) and result = s1.rshift(s2) or + (e instanceof AssignURShiftExpr or e instanceof URShiftExpr) and result = s1.urshift(s2) + ) or + result = exprSign(e.(ConditionalExpr).getTrueExpr()) or + result = exprSign(e.(ConditionalExpr).getFalseExpr()) or + result = exprSign(e.(CastExpr).getExpr()) + ) +} +private Sign binaryOpLhsSign(Expr e) { + result = exprSign(e.(BinaryExpr).getLeftOperand()) or + result = exprSign(e.(AssignOp).getDest()) +} +private Sign binaryOpRhsSign(Expr e) { + result = exprSign(e.(BinaryExpr).getRightOperand()) or + result = exprSign(e.(AssignOp).getRhs()) +} +pragma[noinline] +private predicate binaryOpSigns(Expr e, Sign lhs, Sign rhs) { + lhs = binaryOpLhsSign(e) and + rhs = binaryOpRhsSign(e) +} + +/** Holds if `e` can be positive and cannot be negative. */ +predicate positive(Expr e) { + exprSign(e) = TPos() and + not exprSign(e) = TNeg() +} + +/** Holds if `e` can be negative and cannot be positive. */ +predicate negative(Expr e) { + exprSign(e) = TNeg() and + not exprSign(e) = TPos() +} + +/** Holds if `e` is strictly positive. */ +predicate strictlyPositive(Expr e) { + exprSign(e) = TPos() and + not exprSign(e) = TNeg() and + not exprSign(e) = TZero() +} + +/** Holds if `e` is strictly negative. */ +predicate strictlyNegative(Expr e) { + exprSign(e) = TNeg() and + not exprSign(e) = TPos() and + not exprSign(e) = TZero() +} + diff --git a/java/ql/src/semmle/code/java/dataflow/TaintTracking.qll b/java/ql/src/semmle/code/java/dataflow/TaintTracking.qll new file mode 100644 index 00000000000..3e026b4d338 --- /dev/null +++ b/java/ql/src/semmle/code/java/dataflow/TaintTracking.qll @@ -0,0 +1,689 @@ +/** + * Provides classes for performing local (intra-procedural) and + * global (inter-procedural) taint-tracking analyses. + */ + +import java +import semmle.code.java.dataflow.DataFlow +import semmle.code.java.dataflow.DataFlow2 +import semmle.code.java.Collections + +private import SSA +private import DefUse +private import semmle.code.java.security.SecurityTests +private import semmle.code.java.security.Validation +private import semmle.code.java.frameworks.android.Intent + +module TaintTracking { + + /** + * A taint tracking configuration. + * + * A taint tracking configuration is a special dataflow configuration + * (`DataFlow::Configuration`) that allows for flow through nodes that do not + * necessarily preserve values, but are still relevant from a taint tracking + * perspective. (For example, string concatenation, where one of the operands + * is tainted.) + * + * Each use of the taint tracking library must define its own unique extension + * of this abstract class. A configuration defines a set of relevant sources + * (`isSource`) and sinks (`isSink`), and may additionally treat intermediate + * nodes as "sanitizers" (`isSanitizer`) as well as add custom taint flow steps + * (`isAdditionalTaintStep()`). + */ + abstract class Configuration extends DataFlow::Configuration { + bindingset[this] + Configuration() { any() } + + /** + * Holds if `source` is a relevant taint source. + * + * The smaller this predicate is, the faster `hasFlow()` will converge. + */ + // overridden to provide taint-tracking specific qldoc + abstract override predicate isSource(DataFlow::Node source); + + /** + * Holds if `sink` is a relevant taint sink. + * + * The smaller this predicate is, the faster `hasFlow()` will converge. + */ + // overridden to provide taint-tracking specific qldoc + abstract override predicate isSink(DataFlow::Node sink); + + /** Holds if the node `node` is a taint sanitizer. */ + predicate isSanitizer(DataFlow::Node node) { none() } + + final + override predicate isBarrier(DataFlow::Node node) { + isSanitizer(node) or + // Ignore paths through test code. + node.getEnclosingCallable().getDeclaringType() instanceof NonSecurityTestClass or + exists(ValidatedVariable var | node.asExpr() = var.getAnAccess()) + } + + /** Holds if the edge from `node1` to `node2` is a taint sanitizer. */ + predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() } + + final + override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) { + isSanitizerEdge(node1, node2) + } + + /** + * Holds if the additional taint propagation step from `node1` to `node2` + * must be taken into account in the analysis. + */ + predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() } + + final + override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + isAdditionalTaintStep(node1, node2) or + localAdditionalTaintStep(node1, node2) + } + + /** + * Holds if taint may flow from `source` to `sink` for this configuration. + */ + // overridden to provide taint-tracking specific qldoc + override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) { + super.hasFlow(source, sink) + } + } + + /** + * A taint tracking configuration. + * + * A taint tracking configuration is a special dataflow configuration + * (`DataFlow::Configuration`) that allows for flow through nodes that do not + * necessarily preserve values, but are still relevant from a taint tracking + * perspective. (For example, string concatenation, where one of the operands + * is tainted.) + * + * Each use of the taint tracking library must define its own unique extension + * of this abstract class. A configuration defines a set of relevant sources + * (`isSource`) and sinks (`isSink`), and may additionally treat intermediate + * nodes as "sanitizers" (`isSanitizer`) as well as add custom taint flow steps + * (`isAdditionalTaintStep()`). + */ + abstract class Configuration2 extends DataFlow2::Configuration { + bindingset[this] + Configuration2() { any() } + + /** + * Holds if `source` is a relevant taint source. + * + * The smaller this predicate is, the faster `hasFlow()` will converge. + */ + // overridden to provide taint-tracking specific qldoc + abstract override predicate isSource(DataFlow::Node source); + + /** + * Holds if `sink` is a relevant taint sink. + * + * The smaller this predicate is, the faster `hasFlow()` will converge. + */ + // overridden to provide taint-tracking specific qldoc + abstract override predicate isSink(DataFlow::Node sink); + + /** Holds if the node `node` is a taint sanitizer. */ + predicate isSanitizer(DataFlow::Node node) { none() } + + final + override predicate isBarrier(DataFlow::Node node) { + isSanitizer(node) or + // Ignore paths through test code. + node.getEnclosingCallable().getDeclaringType() instanceof NonSecurityTestClass or + exists(ValidatedVariable var | node.asExpr() = var.getAnAccess()) + } + + /** Holds if the edge from `node1` to `node2` is a taint sanitizer. */ + predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() } + + final + override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) { + isSanitizerEdge(node1, node2) + } + + /** + * Holds if the additional taint propagation step from `node1` to `node2` + * must be taken into account in the analysis. + */ + predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() } + + final + override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + isAdditionalTaintStep(node1, node2) or + localAdditionalTaintStep(node1, node2) + } + + /** + * Holds if taint may flow from `source` to `sink` for this configuration. + */ + // overridden to provide taint-tracking specific qldoc + override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) { + super.hasFlow(source, sink) + } + } + + /** + * Holds if taint can flow from `src` to `sink` in zero or more + * local (intra-procedural) steps. + */ + predicate localTaint(DataFlow::Node src, DataFlow::Node sink) { + localTaintStep*(src, sink) + } + + /** + * Holds if taint can flow in one local step from `src` to `sink`. + */ + predicate localTaintStep(DataFlow::Node src, DataFlow::Node sink) { + DataFlow::localFlowStep(src, sink) or + localAdditionalTaintStep(src, sink) + } + + /** + * Holds if taint can flow in one local step from `src` to `sink` excluding + * local data flow steps. That is, `src` and `sink` are likely to represent + * different objects. + */ + predicate localAdditionalTaintStep(DataFlow::Node src, DataFlow::Node sink) { + localAdditionalTaintExprStep(src.asExpr(), sink.asExpr()) or + exists(Argument arg | src.asExpr() = arg and arg.isVararg() and sink.(DataFlow::ImplicitVarargsArray).getCall() = arg.getCall()) + } + + /** + * Holds if taint can flow in one local step from `src` to `sink` excluding + * local data flow steps. That is, `src` and `sink` are likely to represent + * different objects. + */ + private predicate localAdditionalTaintExprStep(Expr src, Expr sink) { + sink.(AddExpr).getAnOperand() = src and sink.getType() instanceof TypeString or + sink.(AssignAddExpr).getSource() = src and sink.getType() instanceof TypeString or + sink.(ArrayCreationExpr).getInit() = src or + sink.(ArrayInit).getAnInit() = src or + sink.(ArrayAccess).getArray() = src or + sink.(LogicExpr).getAnOperand() = src or + exists(Assignment assign | assign.getSource() = src | + sink = assign.getDest().(ArrayAccess).getArray() + ) or + constructorStep(src, sink) or + qualifierToMethodStep(src, sink) or + qualifierToArgumentStep(src, sink) or + argToMethodStep(src, sink) or + argToArgStep(src, sink) or + argToQualifierStep(src, sink) or + comparisonStep(src, sink) or + stringBuilderStep(src, sink) or + serializationStep(src, sink) or + qualifierToArgStep(src, sink) + } + + private class BulkData extends RefType { + BulkData() { + this.(Array).getElementType().(PrimitiveType).getName().regexpMatch("byte|char") or + exists(RefType t | this.getASourceSupertype*() = t | + t.hasQualifiedName("java.io", "InputStream") or + t.hasQualifiedName("java.nio", "ByteBuffer") or + t.hasQualifiedName("java.lang", "Readable") or + t.hasQualifiedName("java.io", "DataInput") or + t.hasQualifiedName("java.nio.channels", "ReadableByteChannel") + ) + } + } + + /** + * Holds if `c` is a constructor for a subclass of `java.io.InputStream` that + * wraps an underlying data source. The underlying data source is given as a + * the `argi`'th parameter to the constructor. + * + * An object construction of such a wrapper is likely to preserve the data flow + * status of its argument. + */ + private predicate inputStreamWrapper(Constructor c, int argi) { + c.getParameterType(argi) instanceof BulkData and + c.getDeclaringType().getASourceSupertype().hasQualifiedName("java.io", "InputStream") + } + + /** An object construction that preserves the data flow status of any of its arguments. */ + private predicate constructorStep(Expr tracked, ConstructorCall sink) { + exists(int argi | sink.getArgument(argi) = tracked | + exists(string s | sink.getConstructedType().getQualifiedName() = s | + // String constructor does nothing to data + s = "java.lang.String" and argi = 0 or + // some readers preserve the content of streams + s = "java.io.InputStreamReader" and argi = 0 or + s = "java.io.BufferedReader" and argi = 0 or + s = "java.io.CharArrayReader" and argi = 0 or + s = "java.io.StringReader" and argi = 0 or + // data preserved through streams + s = "java.io.ObjectInputStream" and argi = 0 or + s = "java.io.ByteArrayInputStream" and argi = 0 or + s = "java.io.DataInputStream" and argi = 0 or + s = "java.io.BufferedInputStream" and argi = 0 or + s = "com.esotericsoftware.kryo.io.Input" and argi = 0 or + s = "java.beans.XMLDecoder" and argi = 0 or + // a tokenizer preserves the content of a string + s = "java.util.StringTokenizer" and argi = 0 or + // unzipping the stream preserves content + s = "java.util.zip.ZipInputStream" and argi = 0 or + s = "java.util.zip.GZIPInputStream" and argi = 0 or + // string builders and buffers + s = "java.lang.StringBuilder" and argi = 0 or + s = "java.lang.StringBuffer" and argi = 0 or + // a cookie with tainted ingredients is tainted + s = "javax.servlet.http.Cookie" and argi = 0 or + s = "javax.servlet.http.Cookie" and argi = 1 or + // various xml stream source constructors. + s = "org.xml.sax.InputSource" and argi = 0 or + s = "javax.xml.transform.sax.SAXSource" and argi = 0 and sink.getNumArgument() = 1 or + s = "javax.xml.transform.sax.SAXSource" and argi = 1 and sink.getNumArgument() = 2 or + s = "javax.xml.transform.stream.StreamSource" and argi = 0 or + //a URI constructed from a tainted string is tainted. + s = "java.net.URI" and argi = 0 and sink.getNumArgument() = 1 + ) or + exists(RefType t | t.getQualifiedName() = "java.lang.Number" | + hasSubtype*(t, sink.getConstructedType()) + ) and argi = 0 or + // wrappers constructed by extension + exists(Constructor c, Parameter p, SuperConstructorInvocationStmt sup | + c = sink.getConstructor() and + p = c.getParameter(argi) and + sup.getEnclosingCallable() = c and + constructorStep(p.getAnAccess(), sup) + ) or + // a custom InputStream that wraps a tainted data source is tainted + inputStreamWrapper(sink.getConstructor(), argi) + ) + } + + /** Access to a method that passes taint from qualifier to argument. */ + private predicate qualifierToArgumentStep(Expr tracked, RValue sink) { + exists(MethodAccess ma, int arg | + taintPreservingQualifierToArgument(ma.getMethod(), arg) and + tracked = ma.getQualifier() and sink = ma.getArgument(arg) + ) + } + + /** Methods that passes tainted data from qualifier to argument.*/ + private predicate taintPreservingQualifierToArgument(Method m, int arg) { + m instanceof CollectionMethod and + m.hasName("toArray") and arg = 1 + or + m.getDeclaringType().hasQualifiedName("java.io", "ByteArrayOutputStream") and + m.hasName("writeTo") and arg = 0 + } + + /** Access to a method that passes taint from the qualifier. */ + private predicate qualifierToMethodStep(Expr tracked, MethodAccess sink) { + (taintPreservingQualifierToMethod(sink.getMethod()) or unsafeEscape(sink)) + and + tracked = sink.getQualifier() + } + + /** + * Methods that return tainted data when called on tainted data. + */ + private predicate taintPreservingQualifierToMethod(Method m) { + m.getDeclaringType() instanceof TypeString and + ( + m.getName() = "endsWith" or + m.getName() = "getBytes" or + m.getName() = "split" or + m.getName() = "substring" or + m.getName() = "toCharArray" or + m.getName() = "toLowerCase" or + m.getName() = "toString" or + m.getName() = "toUpperCase" or + m.getName() = "trim" + ) or + exists(Class c | c.getQualifiedName() = "java.lang.Number" | hasSubtype*(c, m.getDeclaringType())) and + ( + m.getName().matches("to%String") or + m.getName() = "toByteArray" or + m.getName().matches("%Value") + ) or + m.getDeclaringType().getQualifiedName().matches("%Reader") and + m.getName().matches("read%") + or + m.getDeclaringType().getQualifiedName().matches("%StringWriter") and + m.getName() = "toString" + or + m.getDeclaringType().hasQualifiedName("java.util", "StringTokenizer") and + m.getName().matches("next%") + or + m.getDeclaringType().hasQualifiedName("java.io", "ByteArrayOutputStream") and + (m.getName() = "toByteArray" or m.getName() = "toString") + or + m.getDeclaringType().hasQualifiedName("java.io", "ObjectInputStream") and + m.getName().matches("read%") + or + ( + m.getDeclaringType().hasQualifiedName("java.lang", "StringBuilder") or + m.getDeclaringType().hasQualifiedName("java.lang", "StringBuffer") + ) and + (m.getName() = "toString" or m.getName() = "append") + or + m.getDeclaringType().hasQualifiedName("javax.xml.transform.sax", "SAXSource") and + m.hasName("getInputSource") + or + m.getDeclaringType().hasQualifiedName("javax.xml.transform.stream", "StreamSource") and + m.hasName("getInputStream") + or + m instanceof IntentGetExtraMethod + or + m instanceof CollectionMethod and + m.hasName("toArray") + or + m.getDeclaringType().hasQualifiedName("java.nio", "ByteBuffer") and + m.hasName("get") + } + + private class StringReplaceMethod extends Method { + StringReplaceMethod() { + getDeclaringType() instanceof TypeString and + ( + hasName("replace") or + hasName("replaceAll") or + hasName("replaceFirst") + ) + } + } + + private predicate unsafeEscape(MethodAccess ma) { + // Removing `