Skip to content

Commit e0641f2

Browse files
committed
feat: add counting loop repetition to path duration
1 parent 13df7ac commit e0641f2

File tree

11 files changed

+290
-7
lines changed

11 files changed

+290
-7
lines changed

common/src/main/kotlin/spp/jetbrains/artifact/model/ControlStructureArtifact.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616
*/
1717
package spp.jetbrains.artifact.model
1818

19+
import com.intellij.psi.PsiElement
20+
1921
/**
2022
* A control structure artifact refers to things that control the flow of a program:
2123
* - conditionals (i.e. if/elif/else)
2224
* - for-loops, while-loops, do-while loops
2325
* - try-catch-finally
2426
* - switch statements
2527
*/
26-
interface ControlStructureArtifact {
27-
val childArtifacts: List<ArtifactElement>
28+
abstract class ControlStructureArtifact(psiElement: PsiElement) : ArtifactElement(psiElement) {
29+
abstract val childArtifacts: MutableList<ArtifactElement>
2830
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Source++, the continuous feedback platform for developers.
3+
* Copyright (C) 2022-2023 CodeBrig, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package spp.jetbrains.artifact.model
18+
19+
import com.intellij.psi.PsiElement
20+
import com.intellij.psi.PsiExpression
21+
import com.intellij.psi.PsiLocalVariable
22+
import spp.jetbrains.artifact.service.toArtifact
23+
24+
abstract class CountingLoopArtifact(psiElement: PsiElement) : ForLoopArtifact(psiElement) {
25+
26+
/**
27+
* @return loop counter variable
28+
*/
29+
abstract fun getCounter(): PsiLocalVariable
30+
31+
/**
32+
* @return counter variable initial value
33+
*/
34+
abstract fun getInitializer(): PsiExpression
35+
36+
/**
37+
* @return loop bound
38+
*/
39+
abstract fun getBound(): PsiExpression
40+
41+
/**
42+
* @return true if bound is including
43+
*/
44+
abstract fun isIncluding(): Boolean
45+
46+
/**
47+
* @return true if the loop is descending
48+
*/
49+
abstract fun isDescending(): Boolean
50+
51+
/**
52+
* @return true if the loop variable may experience integer overflow before reaching the bound,
53+
* like for(int i = 10; i != -10; i++) will go through MAX_VALUE and MIN_VALUE.
54+
*/
55+
abstract fun mayOverflow(): Boolean
56+
57+
fun getRepetitionCount(): Long? {
58+
val initializer = getInitializer().toArtifact()
59+
val bound = getBound().toArtifact()
60+
61+
if (initializer is ArtifactLiteralValue && bound is ArtifactLiteralValue) {
62+
val initValue = initializer.value?.toString()?.toLongOrNull()
63+
val boundValue = bound.value?.toString()?.toLongOrNull()
64+
if (initValue != null && boundValue != null) {
65+
val diff = boundValue - initValue
66+
return if (isIncluding()) diff + 1 else diff
67+
}
68+
}
69+
return null
70+
}
71+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Source++, the continuous feedback platform for developers.
3+
* Copyright (C) 2022-2023 CodeBrig, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package spp.jetbrains.artifact.model
18+
19+
import com.intellij.psi.PsiElement
20+
21+
abstract class ForLoopArtifact(psiElement: PsiElement) : LoopArtifact(psiElement)

common/src/main/kotlin/spp/jetbrains/artifact/model/IfArtifact.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import com.intellij.psi.PsiElement
2121
/**
2222
* Represents if/elif/else control structures.
2323
*/
24-
abstract class IfArtifact(psiElement: PsiElement) : ArtifactElement(psiElement), ControlStructureArtifact {
24+
abstract class IfArtifact(psiElement: PsiElement) : ControlStructureArtifact(psiElement) {
2525

2626
abstract val condition: ArtifactElement?
2727
abstract val thenBranch: ArtifactElement?
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Source++, the continuous feedback platform for developers.
3+
* Copyright (C) 2022-2023 CodeBrig, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package spp.jetbrains.artifact.model
18+
19+
import com.intellij.psi.PsiElement
20+
21+
/**
22+
* Represents for/while/do-while control structures.
23+
*/
24+
abstract class LoopArtifact(psiElement: PsiElement) : ControlStructureArtifact(psiElement) {
25+
abstract val condition: ArtifactElement?
26+
abstract val body: ArtifactElement?
27+
}

insight/src/main/kotlin/spp/jetbrains/insight/ProceduralAnalyzer.kt

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,7 @@
1616
*/
1717
package spp.jetbrains.insight
1818

19-
import spp.jetbrains.artifact.model.ArtifactElement
20-
import spp.jetbrains.artifact.model.BlockArtifact
21-
import spp.jetbrains.artifact.model.FunctionArtifact
22-
import spp.jetbrains.artifact.model.IfArtifact
19+
import spp.jetbrains.artifact.model.*
2320
import spp.jetbrains.artifact.service.getParentFunction
2421
import spp.jetbrains.artifact.service.toArtifact
2522
import java.util.*
@@ -91,6 +88,13 @@ class ProceduralAnalyzer {
9188
walkDown(elseBranch, elseArtifacts)
9289
}
9390
parent.add(elseArtifacts)
91+
} else if (descendant is LoopArtifact) {
92+
val loopBody = descendant.body
93+
val loopBodyArtifacts = mutableListOf<Any>()
94+
if (loopBody != null) {
95+
walkDown(loopBody, loopBodyArtifacts)
96+
}
97+
parent.add(loopBodyArtifacts)
9498
} else {
9599
walkDown(descendant, parent)
96100
}
@@ -122,6 +126,9 @@ class ProceduralAnalyzer {
122126
val childArtifacts = processArtifacts[index + 2] as List<Any>
123127
processPath(path, artifactElement.childArtifacts, childArtifacts, boolIndex)
124128
}
129+
} else if (artifactElement is LoopArtifact) {
130+
val childArtifacts = processArtifacts[index + 1] as List<Any>
131+
processPath(path, artifactElement.childArtifacts, childArtifacts, boolIndex)
125132
}
126133
}
127134
}

insight/src/main/kotlin/spp/jetbrains/insight/pass/path/PathDurationPass.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
package spp.jetbrains.insight.pass.path
1818

1919
import spp.jetbrains.artifact.model.ArtifactElement
20+
import spp.jetbrains.artifact.model.CountingLoopArtifact
2021
import spp.jetbrains.artifact.model.IfArtifact
22+
import spp.jetbrains.artifact.model.LoopArtifact
2123
import spp.jetbrains.insight.InsightKeys
2224
import spp.jetbrains.insight.ProceduralPath
2325
import spp.jetbrains.insight.getDuration
@@ -53,6 +55,20 @@ class PathDurationPass : ProceduralPathPass {
5355
if (executionProbability == null || executionProbability.value > 0.0) {
5456
analyze(it.childArtifacts, duration)?.let { duration = it }
5557
}
58+
} else if (it is LoopArtifact) {
59+
val bodyDuration = it.childArtifacts.mapNotNull { it.getDuration() }
60+
.takeIf { it.isNotEmpty() }?.sum()
61+
if (it is CountingLoopArtifact) {
62+
val repetitionCount = it.getRepetitionCount()
63+
duration = if (repetitionCount != null) {
64+
duration?.plus(bodyDuration?.times(repetitionCount) ?: 0)
65+
?: bodyDuration?.times(repetitionCount)
66+
} else {
67+
duration?.plus(bodyDuration ?: 0) ?: bodyDuration
68+
}
69+
} else {
70+
duration = duration?.plus(bodyDuration ?: 0) ?: bodyDuration
71+
}
5672
} else {
5773
val artifactDuration = it.getDuration()
5874
if (artifactDuration != null) {
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Source++, the continuous feedback platform for developers.
3+
* Copyright (C) 2022-2023 CodeBrig, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package spp.jetbrains.insight.pass.artifact
18+
19+
import com.intellij.testFramework.TestDataPath
20+
import com.intellij.testFramework.fixtures.BasePlatformTestCase
21+
import org.junit.jupiter.api.Test
22+
import spp.jetbrains.artifact.service.getCalls
23+
import spp.jetbrains.artifact.service.getFunctions
24+
import spp.jetbrains.artifact.service.toArtifact
25+
import spp.jetbrains.insight.InsightKeys
26+
import spp.jetbrains.insight.ProceduralAnalyzer
27+
import spp.jetbrains.marker.jvm.JVMLanguageProvider
28+
import spp.jetbrains.marker.service.*
29+
import spp.protocol.insight.InsightType
30+
import spp.protocol.insight.InsightValue
31+
32+
@TestDataPath("\$CONTENT_ROOT/testData/")
33+
class CountingLoopPassTest : BasePlatformTestCase() {
34+
35+
override fun setUp() {
36+
super.setUp()
37+
38+
JVMLanguageProvider().setup(project)
39+
}
40+
41+
override fun getTestDataPath(): String {
42+
return "src/test/testData/"
43+
}
44+
45+
@Test
46+
fun testCountingLoop() {
47+
// doTest("kotlin", "kt")
48+
doTest("java", "java")
49+
}
50+
51+
private fun doTest(language: String, extension: String) {
52+
val psi = myFixture.configureByFile("$language/CountingLoop.$extension")
53+
54+
//setup
55+
psi.getCalls().filter { it.text.contains("true", true) }.forEach {
56+
it.putUserData(
57+
InsightKeys.FUNCTION_DURATION.asPsiKey(),
58+
InsightValue.of(InsightType.FUNCTION_DURATION, 100L)
59+
)
60+
}
61+
62+
val countingLoop1 = psi.getFunctions().first { it.name == "countingLoop" }
63+
val loop1Paths = ProceduralAnalyzer().analyze(countingLoop1.toArtifact()!!)
64+
assertEquals(1, loop1Paths.size)
65+
val loop1Path = loop1Paths.first()
66+
val loop1PathInsights = loop1Path.getInsights()
67+
assertEquals(1, loop1PathInsights.size)
68+
assertEquals(InsightType.PATH_DURATION, loop1PathInsights.first().type)
69+
assertEquals(1000L, loop1PathInsights.first().value)
70+
}
71+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
public class CountingLoop {
2+
public void countingLoop() {
3+
for (int i = 0; i < 10; i++) {
4+
System.out.println(true); //sleep 100ms
5+
}
6+
for (int i = 0; i < 10; i++) ;
7+
}
8+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Source++, the continuous feedback platform for developers.
3+
* Copyright (C) 2022-2023 CodeBrig, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package spp.jetbrains.marker.jvm.model
18+
19+
import com.intellij.psi.PsiConditionalLoopStatement
20+
import com.intellij.psi.PsiExpression
21+
import com.intellij.psi.PsiLocalVariable
22+
import com.intellij.psi.PsiLoopStatement
23+
import com.siyeh.ig.psiutils.CountingLoop
24+
import spp.jetbrains.artifact.model.ArtifactElement
25+
import spp.jetbrains.artifact.model.CountingLoopArtifact
26+
import spp.jetbrains.artifact.service.toArtifact
27+
28+
class JVMCountingLoop(private val loop: CountingLoop) : CountingLoopArtifact(loop.loop) {
29+
30+
override val childArtifacts: MutableList<ArtifactElement> = mutableListOf()
31+
override val condition: ArtifactElement? = when (psiElement) {
32+
is PsiConditionalLoopStatement -> psiElement.condition?.let { it.toArtifact() }
33+
else -> null
34+
}
35+
override val body: ArtifactElement? = when (psiElement) {
36+
is PsiLoopStatement -> psiElement.body?.let { it.toArtifact() }
37+
else -> null
38+
}
39+
40+
override fun getCounter(): PsiLocalVariable = loop.counter
41+
override fun getInitializer(): PsiExpression = loop.initializer
42+
override fun getBound(): PsiExpression = loop.bound
43+
override fun isIncluding(): Boolean = loop.isIncluding
44+
override fun isDescending(): Boolean = loop.isDescending
45+
override fun mayOverflow(): Boolean = loop.mayOverflow()
46+
47+
override fun clone(): ArtifactElement {
48+
return JVMCountingLoop(loop)
49+
}
50+
}

0 commit comments

Comments
 (0)