Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP #252

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft

WIP #252

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion its/sources
Submodule sources updated 232 files
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package checks

import java.io.File
import java.io.FileOutputStream
import java.io.ObjectOutputStream
import java.nio.file.Files
import java.nio.file.OpenOption
import java.nio.file.Paths
import java.nio.file.StandardOpenOption

class ObjectOutputStreamCheckSample {

fun noncompliant_1(fileName: String?) {
val fos = FileOutputStream(fileName, true) // fos opened in append mode
val out = ObjectOutputStream(fos) // Noncompliant {{Do not use a FileOutputStream in append mode.}}
}


fun noncompliant_2(fileName: String?, appendMode: Boolean) {
if (!appendMode) return
val fos = FileOutputStream(fileName, appendMode) // fos opened in append mode
val out = ObjectOutputStream(fos) // FN
}


fun noncompliant_3(file: File?) {
val fos = FileOutputStream(file, true) // fos opened in append mode
val out = ObjectOutputStream(fos) // Noncompliant
}


fun noncompliant_10() {
val fos = Files.newOutputStream(
Paths.get("a"),
StandardOpenOption.APPEND
)
val out = ObjectOutputStream(fos) // Noncompliant [[flows=f1]]
}


fun noncompliant_11() {
val fos = Files.newOutputStream(Paths.get("a"), StandardOpenOption.DELETE_ON_CLOSE, StandardOpenOption.APPEND)
val out = ObjectOutputStream(fos) // Noncompliant
}


fun noncompliant_12() {
val openOption: OpenOption = StandardOpenOption.APPEND
val fos = Files.newOutputStream(Paths.get("a"), StandardOpenOption.DELETE_ON_CLOSE, openOption)
val out = ObjectOutputStream(fos) // Noncompliant
}


fun noncompliant_13() {
val fos = Files.newOutputStream(Paths.get("a"), StandardOpenOption.APPEND)
val out = ObjectOutputStream(fos) // Noncompliant
}


fun compliant_1(fileName: String?) {
val fos = FileOutputStream(fileName, false)
val out = ObjectOutputStream(fos)
}


fun compliant_2(fileName: String?) {
val fos = FileOutputStream(fileName)
val out = ObjectOutputStream(fos)
}


fun compliant_10() {
val fos = Files.newOutputStream(Paths.get("a"), StandardOpenOption.TRUNCATE_EXISTING)
val out = ObjectOutputStream(fos)
}


fun coverage() {
val out = ObjectOutputStream(null)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* SonarSource Kotlin
* Copyright (C) 2018-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonarsource.kotlin.checks

import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument
import org.jetbrains.kotlin.resolve.calls.util.getCall
import org.jetbrains.kotlin.resolve.calls.util.getFirstArgumentExpression
import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
import org.jetbrains.kotlin.utils.addToStdlib.ifTrue
import org.sonar.check.Rule
import org.sonarsource.kotlin.api.*
import org.sonarsource.kotlin.plugin.KotlinFileContext

val fileOutputStreamConstructorMatcher = ConstructorMatcher(typeName = "java.io.FileOutputStream") {
withArguments("kotlin.String", "kotlin.Boolean")
withArguments("java.io.File", "kotlin.Boolean")
}

val filesNewOutputStreamMatcher = FunMatcher(qualifier = "java.nio.file.Files") {
withArguments(
ArgumentMatcher(typeName = "java.nio.file.Path"),
ArgumentMatcher(typeName = "java.nio.file.OpenOption", isVararg = true),

)
}

const val APPEND = "java.nio.file.StandardOpenOption.APPEND"

@Rule(key = "S2689")
class ObjectOutputStreamCheck : CallAbstractCheck() {
override val functionsToVisit: Iterable<FunMatcherImpl> =
listOf(ConstructorMatcher(typeName = "java.io.ObjectOutputStream") {
withArguments("java.io.OutputStream")
})

override fun visitFunctionCall(
callExpression: KtCallExpression,
resolvedCall: ResolvedCall<*>,
kotlinFileContext: KotlinFileContext
) {
val bindingContext = kotlinFileContext.bindingContext
callExpression.getResolvedCall(bindingContext)?.getFirstArgumentExpression()
?.let { arg ->
arg.predictRuntimeValueExpression(bindingContext).getCall(bindingContext)
?.also { call ->
if (fileOutputStreamConstructorMatcher.matches(call, bindingContext)) {
(call.getResolvedCall(bindingContext)?.valueArgumentsByIndex?.get(1) as? ExpressionValueArgument)
?.valueArgument?.getArgumentExpression()?.predictRuntimeBooleanValue(bindingContext)
?.ifTrue {
kotlinFileContext.reportIssue(
callExpression.calleeExpression!!,
"Do not use a FileOutputStream in append mode."
)
}
} else if (filesNewOutputStreamMatcher.matches(call, bindingContext)) {

val varargValueArgument =
call.getResolvedCall(bindingContext)?.valueArgumentsByIndex?.get(1) as? VarargValueArgument ?: return
varargValueArgument.arguments.any {
(it.getArgumentExpression()?.predictRuntimeValueExpression(bindingContext) as? KtDotQualifiedExpression)
?.resolveReferenceTarget(bindingContext).determineType()?.getJetTypeFqName(false) == APPEND
}.ifTrue {
kotlinFileContext.reportIssue(
callExpression.calleeExpression!!,
"Do not use a FileOutputStream in append mode."
)
}
}
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import org.sonarsource.kotlin.checks.MainSafeCoroutinesCheck
import org.sonarsource.kotlin.checks.MatchCaseTooBigCheck
import org.sonarsource.kotlin.checks.MobileDatabaseEncryptionKeysCheck
import org.sonarsource.kotlin.checks.NestedMatchCheck
import org.sonarsource.kotlin.checks.ObjectOutputStreamCheck
import org.sonarsource.kotlin.checks.OneStatementPerLineCheck
import org.sonarsource.kotlin.checks.ParsingErrorCheck
import org.sonarsource.kotlin.checks.PseudoRandomCheck
Expand Down Expand Up @@ -183,6 +184,7 @@ val KOTLIN_CHECKS = listOf(
MatchCaseTooBigCheck::class.java,
MobileDatabaseEncryptionKeysCheck::class.java,
NestedMatchCheck::class.java,
ObjectOutputStreamCheck::class.java,
OneStatementPerLineCheck::class.java,
ParsingErrorCheck::class.java,
PseudoRandomCheck::class.java,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<p><code>ObjectOutputStream</code>s are used with serialization, and the first thing an <code>ObjectOutputStream</code> writes is the serialization
stream header. This header should appear once per file, at the beginning. Pass a file opened in append mode into an <code>ObjectOutputStream</code>
constructor and the serialization stream header will be added to the end of the file before your object is then also appended.</p>
<p>When you’re trying to read your object(s) back from the file, only the first one will be read successfully, and a
<code>StreamCorruptedException</code> will be thrown after that.</p>
<h2>Noncompliant Code Example</h2>
<pre>
val fos = FileOutputStream(fileName, true) // fos opened in append mode
val out = ObjectOutputStream(fos) // Noncompliant
</pre>
<h2>Compliant Solution</h2>
<pre>
val fos = FileOutputStream(fileName)
val out = ObjectOutputStream(fos)
</pre>

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"title": "Files opened in append mode should not be used with ObjectOutputStream",
"type": "BUG",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "1h"
},
"tags": [
"serialization"
],
"defaultSeverity": "Blocker",
"ruleSpecification": "RSPEC-2689",
"sqKey": "S2689",
"scope": "Main",
"quickfix": "unknown"
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"S2151",
"S2175",
"S2245",
"S2689",
"S2757",
"S3329",
"S3776",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* SonarSource Kotlin
* Copyright (C) 2018-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonarsource.kotlin.checks

internal class ObjectOutputStreamCheckTest : CheckTest(ObjectOutputStreamCheck())