Kotlin: support argument-range specifications for $default methods

This commit is contained in:
Chris Smowton
2022-10-24 19:15:58 +01:00
parent 8d10b1b77b
commit 7a0bded2ac
4 changed files with 31 additions and 11 deletions

View File

@@ -8,6 +8,9 @@ class LibClass {
fun memberWithDefaults(x: Int, y: Int = 1) = 0 fun memberWithDefaults(x: Int, y: Int = 1) = 0
fun String.extensionMemberWithDefaults(x: Int, y: Int = 1) = 0 fun String.extensionMemberWithDefaults(x: Int, y: Int = 1) = 0
fun multiParameterTest(x: Int, y: Int, z: Int, w: Int = 0) = 0
fun Int.multiParameterExtensionTest(x: Int, y: Int, w: Int = 0) = 0
} }
class SomeToken {} class SomeToken {}
@@ -30,4 +33,3 @@ class SinkClass(x: Int, y: Int = 1) {
fun String.extensionMemberSink(x: Int, y: Int = 1) {} fun String.extensionMemberSink(x: Int, y: Int = 1) {}
} }

View File

@@ -11,7 +11,9 @@ private class Models extends SummaryModelCsv {
";LibKt;true;topLevelWithDefaults;(int,int);;Argument[0];ReturnValue;value;manual", ";LibKt;true;topLevelWithDefaults;(int,int);;Argument[0];ReturnValue;value;manual",
";LibKt;true;extensionWithDefaults;(String,int,int);;Argument[1];ReturnValue;value;manual", ";LibKt;true;extensionWithDefaults;(String,int,int);;Argument[1];ReturnValue;value;manual",
";LibClass;true;memberWithDefaults;(int,int);;Argument[0];ReturnValue;value;manual", ";LibClass;true;memberWithDefaults;(int,int);;Argument[0];ReturnValue;value;manual",
";LibClass;true;extensionMemberWithDefaults;(String,int,int);;Argument[1];ReturnValue;value;manual" ";LibClass;true;extensionMemberWithDefaults;(String,int,int);;Argument[1];ReturnValue;value;manual",
";LibClass;true;multiParameterTest;(int,int,int,int);;Argument[0..1];ReturnValue;value;manual",
";LibClass;true;multiParameterExtensionTest;(int,int,int,int);;Argument[0, 1];ReturnValue;value;manual",
] ]
} }
} }

View File

@@ -16,10 +16,20 @@ fun test(c: LibClass, sourcec: SourceClass, sinkc: SinkClass) {
sink(c.memberWithDefaults(source(), 0)) // $ flow sink(c.memberWithDefaults(source(), 0)) // $ flow
sink(c.memberWithDefaults(source())) // $ flow sink(c.memberWithDefaults(source())) // $ flow
sink(c.multiParameterTest(source(), 0, 0)) // $ flow
sink(c.multiParameterTest(0, source(), 0)) // $ flow
sink(c.multiParameterTest(0, 0, source()))
with(c) { with(c) {
sink("Hello world".extensionMemberWithDefaults(source(), 0)) // $ flow sink("Hello world".extensionMemberWithDefaults(source(), 0)) // $ flow
sink("Hello world".extensionMemberWithDefaults(source())) // $ flow sink("Hello world".extensionMemberWithDefaults(source())) // $ flow
}; }
with(c) {
sink(source().multiParameterExtensionTest(0, 0)) // $ flow
sink(0.multiParameterExtensionTest(source(), 0)) // $ flow
sink(0.multiParameterExtensionTest(0, source()))
}
run { run {
val st = SomeToken() val st = SomeToken()

View File

@@ -10,6 +10,7 @@ private import FlowSummaryImpl::Private
private import FlowSummaryImpl::Public private import FlowSummaryImpl::Public
private import semmle.code.java.dataflow.ExternalFlow private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSummary as FlowSummary private import semmle.code.java.dataflow.FlowSummary as FlowSummary
private import semmle.code.java.dataflow.internal.AccessPathSyntax as AccessPathSyntax
class SummarizedCallableBase = FlowSummary::SummarizedCallableBase; class SummarizedCallableBase = FlowSummary::SummarizedCallableBase;
@@ -84,6 +85,9 @@ private predicate relatedArgSpec(Callable c, string spec) {
* for the additional dispatch receiver parameter that occurs in the default-parameter proxy's argument * for the additional dispatch receiver parameter that occurs in the default-parameter proxy's argument
* list. When no adjustment is required (e.g. for constructors, or non-argument-based specs), `defaultArgsSpec` * list. When no adjustment is required (e.g. for constructors, or non-argument-based specs), `defaultArgsSpec`
* equals `originalArgSpec`. * equals `originalArgSpec`.
*
* Note in the case where `originalArgSpec` uses an integer range, like `Argument[1..3]...`, this will produce multiple
* results for `defaultsArgSpec`, like `{Argument[2]..., Argument[3]..., Argument[4]...}`.
*/ */
private predicate correspondingKotlinParameterDefaultsArgSpec( private predicate correspondingKotlinParameterDefaultsArgSpec(
Callable originalCallable, Callable defaultsCallable, string originalArgSpec, Callable originalCallable, Callable defaultsCallable, string originalArgSpec,
@@ -98,19 +102,21 @@ private predicate correspondingKotlinParameterDefaultsArgSpec(
exists(string regex | exists(string regex |
// Note I use a regex and not AccessPathToken because this feeds summaryElement et al, // Note I use a regex and not AccessPathToken because this feeds summaryElement et al,
// which would introduce mutual recursion with the definition of AccessPathToken. // which would introduce mutual recursion with the definition of AccessPathToken.
regex = "Argument\\[([0-9]+)\\](.*)" and regex = "Argument\\[([0-9,\\. ]+)\\](.*)" and
( (
exists(string oldArgNumber, string rest, int paramOffset | exists(string oldArgNumber, string rest, int paramOffset |
oldArgNumber = originalArgSpec.regexpCapture(regex, 1) and oldArgNumber = originalArgSpec.regexpCapture(regex, 1) and
rest = originalArgSpec.regexpCapture(regex, 2) and rest = originalArgSpec.regexpCapture(regex, 2) and
paramOffset = paramOffset =
( defaultsCallable.getNumberOfParameters() -
defaultsCallable.getNumberOfParameters() - (originalCallable.getNumberOfParameters() + 2) and
(originalCallable.getNumberOfParameters() + 2) exists(int oldArgParsed |
) and oldArgParsed = AccessPathSyntax::AccessPath::parseInt(oldArgNumber.splitAt(",").trim())
if ktExtensionFunctions(originalCallable, _, _) and oldArgNumber = "0" |
then defaultsArgSpec = originalArgSpec if ktExtensionFunctions(originalCallable, _, _) and oldArgParsed = 0
else defaultsArgSpec = "Argument[" + (oldArgNumber.toInt() + paramOffset) + "]" + rest then defaultsArgSpec = "Argument[0]"
else defaultsArgSpec = "Argument[" + (oldArgParsed + paramOffset) + "]" + rest
)
) )
or or
not originalArgSpec.regexpMatch(regex) and not originalArgSpec.regexpMatch(regex) and