Skip to content

Commit bca2586

Browse files
committed
Kotlin: Populate numfiles
1 parent 10eb548 commit bca2586

File tree

5 files changed

+143
-4
lines changed

5 files changed

+143
-4
lines changed

java/kotlin-extractor/src/main/kotlin/ExternalDeclExtractor.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ class ExternalDeclExtractor(val logger: FileLogger, val invocationTrapFile: Stri
8484
// file information if needed:
8585
val ftw = tw.makeFileTrapWriter(binaryPath, irDecl is IrClass)
8686

87-
val fileExtractor = KotlinFileExtractor(logger, ftw, binaryPath, manager, this, primitiveTypeMapping, pluginContext, KotlinFileExtractor.DeclarationStack(), globalExtensionState)
87+
val fileExtractor = KotlinFileExtractor(logger, ftw, null, binaryPath, manager, this, primitiveTypeMapping, pluginContext, KotlinFileExtractor.DeclarationStack(), globalExtensionState)
8888

8989
if (irDecl is IrClass) {
9090
// Populate a location and compilation-unit package for the file. This is similar to

java/kotlin-extractor/src/main/kotlin/KotlinExtractorExtension.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,8 @@ private fun doFile(
322322
// file information
323323
val sftw = tw.makeSourceFileTrapWriter(srcFile, true)
324324
val externalDeclExtractor = ExternalDeclExtractor(logger, invocationTrapFile, srcFilePath, primitiveTypeMapping, pluginContext, globalExtensionState, fileTrapWriter)
325-
val fileExtractor = KotlinFileExtractor(logger, sftw, srcFilePath, null, externalDeclExtractor, primitiveTypeMapping, pluginContext, KotlinFileExtractor.DeclarationStack(), globalExtensionState)
325+
val linesOfCode = LinesOfCode(logger, sftw, srcFile)
326+
val fileExtractor = KotlinFileExtractor(logger, sftw, linesOfCode, srcFilePath, null, externalDeclExtractor, primitiveTypeMapping, pluginContext, KotlinFileExtractor.DeclarationStack(), globalExtensionState)
326327

327328
fileExtractor.extractFileContents(srcFile, sftw.fileId)
328329
externalDeclExtractor.extractExternalClasses()

java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import kotlin.collections.ArrayList
3535
open class KotlinFileExtractor(
3636
override val logger: FileLogger,
3737
override val tw: FileTrapWriter,
38+
val linesOfCode: LinesOfCode?,
3839
val filePath: String,
3940
dependencyCollector: OdasaOutput.TrapFileManager?,
4041
externalClassExtractor: ExternalDeclExtractor,
@@ -90,6 +91,8 @@ open class KotlinFileExtractor(
9091
if (!declarationStack.isEmpty()) {
9192
logger.errorElement("Declaration stack is not empty after processing the file", file)
9293
}
94+
95+
linesOfCode?.linesOfCodeInFile(id)
9396
}
9497
}
9598

@@ -459,6 +462,8 @@ open class KotlinFileExtractor(
459462
extractClassModifiers(c, id)
460463
extractClassSupertypes(c, id, inReceiverContext = true) // inReceiverContext = true is specified to force extraction of member prototypes of base types
461464

465+
linesOfCode?.linesOfCodeInDeclaration(c, id)
466+
462467
return id
463468
}
464469
}
@@ -1038,6 +1043,8 @@ open class KotlinFileExtractor(
10381043
addModifiers(id, "suspend")
10391044
}
10401045

1046+
linesOfCode?.linesOfCodeInDeclaration(f, id)
1047+
10411048
return id
10421049
}
10431050
}

java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,13 @@ open class KotlinUsesExtractor(
138138
val newTrapWriter = tw.makeFileTrapWriter(filePath, true)
139139
val newLoggerTrapWriter = logger.tw.makeFileTrapWriter(filePath, false)
140140
val newLogger = FileLogger(logger.loggerBase, newLoggerTrapWriter)
141-
return KotlinFileExtractor(newLogger, newTrapWriter, filePath, dependencyCollector, externalClassExtractor, primitiveTypeMapping, pluginContext, newDeclarationStack, globalExtensionState)
141+
return KotlinFileExtractor(newLogger, newTrapWriter, null, filePath, dependencyCollector, externalClassExtractor, primitiveTypeMapping, pluginContext, newDeclarationStack, globalExtensionState)
142142
}
143143

144144
val newTrapWriter = tw.makeSourceFileTrapWriter(clsFile, true)
145145
val newLoggerTrapWriter = logger.tw.makeSourceFileTrapWriter(clsFile, false)
146146
val newLogger = FileLogger(logger.loggerBase, newLoggerTrapWriter)
147-
return KotlinFileExtractor(newLogger, newTrapWriter, clsFile.path, dependencyCollector, externalClassExtractor, primitiveTypeMapping, pluginContext, newDeclarationStack, globalExtensionState)
147+
return KotlinFileExtractor(newLogger, newTrapWriter, null, clsFile.path, dependencyCollector, externalClassExtractor, primitiveTypeMapping, pluginContext, newDeclarationStack, globalExtensionState)
148148
}
149149

150150
// The Kotlin compiler internal representation of Outer<T>.Inner<S>.InnerInner<R> is InnerInner<R, S, T>. This function returns just `R`.
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package com.github.codeql
2+
3+
import com.github.codeql.utils.versions.Psi2Ir
4+
import com.intellij.psi.PsiComment
5+
import com.intellij.psi.PsiElement
6+
import com.intellij.psi.PsiWhiteSpace
7+
import org.jetbrains.kotlin.ir.IrElement
8+
import org.jetbrains.kotlin.ir.declarations.*
9+
import org.jetbrains.kotlin.kdoc.psi.api.KDocElement
10+
import org.jetbrains.kotlin.psi.KtCodeFragment
11+
import org.jetbrains.kotlin.psi.KtVisitor
12+
13+
class LinesOfCode(
14+
val logger: FileLogger,
15+
val tw: FileTrapWriter,
16+
val file: IrFile
17+
) {
18+
val psi2Ir = Psi2Ir(logger)
19+
20+
fun linesOfCodeInFile(id: Label<DbFile>) {
21+
val ktFile = psi2Ir.getKtFile(file)
22+
if (ktFile == null) {
23+
logger.warnElement("Cannot find PSI for file", file)
24+
println("No KtFile")
25+
return
26+
}
27+
linesOfCodeInPsi(id, ktFile, file)
28+
}
29+
30+
fun linesOfCodeInDeclaration(d: IrDeclaration, id: Label<out DbSourceline>) {
31+
val p = psi2Ir.findPsiElement(d, file)
32+
if (p == null) {
33+
logger.warnElement("Cannot find PSI for declaration: " + d.javaClass, d)
34+
println("No p")
35+
return
36+
}
37+
linesOfCodeInPsi(id, p, d)
38+
}
39+
40+
private fun linesOfCodeInPsi(id: Label<out DbSourceline>, root: PsiElement, e: IrElement) {
41+
val document = root.getContainingFile().getViewProvider().getDocument()
42+
if (document == null) {
43+
logger.errorElement("Cannot find document for PSI", e)
44+
tw.writeNumlines(id, 0, 0, 0)
45+
return
46+
}
47+
48+
val rootRange = root.getTextRange()
49+
val rootFirstLine = document.getLineNumber(rootRange.getStartOffset())
50+
val rootLastLine = document.getLineNumber(rootRange.getEndOffset())
51+
if (rootLastLine < rootFirstLine) {
52+
logger.errorElement("PSI ends before it starts", e)
53+
tw.writeNumlines(id, 0, 0, 0)
54+
return
55+
}
56+
val numLines = 1 + rootLastLine - rootFirstLine
57+
val lineContents = Array(numLines) { LineContent() }
58+
59+
val visitor =
60+
object : KtVisitor<Unit, Unit>() {
61+
override fun visitElement(element: PsiElement) {
62+
val isComment = element is PsiComment
63+
// Comments may include nodes that aren't PsiComments,
64+
// so we don't want to visit them or we'll think they
65+
// are code.
66+
if (!isComment) {
67+
element.acceptChildren(this)
68+
}
69+
70+
if (element is PsiWhiteSpace) {
71+
return
72+
}
73+
// Leaf nodes are assumed to be tokens, and
74+
// therefore we count any lines that they are on.
75+
// For comments, we actually need to look at the
76+
// outermost node, as the leaves of KDocs don't
77+
// necessarily cover all lines.
78+
if (isComment || element.getChildren().size == 0) {
79+
val range = element.getTextRange()
80+
val startOffset = range.getStartOffset()
81+
val endOffset = range.getEndOffset()
82+
// The PSI doesn't seem to have anything like
83+
// the IR's UNDEFINED_OFFSET and SYNTHETIC_OFFSET,
84+
// but < 0 still seem to represent bad/unknown
85+
// locations.
86+
if (startOffset < 0 || endOffset < 0) {
87+
logger.errorElement("PSI has negative offset", e)
88+
return
89+
}
90+
if (startOffset > endOffset) {
91+
return
92+
}
93+
// We might get e.g. an import list for a file
94+
// with no imports, which claims to have start
95+
// and end offsets of 0. Anything of 0 width
96+
// we therefore just skip.
97+
if (startOffset == endOffset) {
98+
return
99+
}
100+
val firstLine = document.getLineNumber(startOffset)
101+
val lastLine = document.getLineNumber(endOffset)
102+
if (firstLine < rootFirstLine) {
103+
logger.errorElement("PSI element starts before root", e)
104+
return
105+
} else if (lastLine > rootLastLine) {
106+
logger.errorElement("PSI element ends after root", e)
107+
return
108+
}
109+
for (line in firstLine..lastLine) {
110+
val lineContent = lineContents[line - rootFirstLine]
111+
if (isComment) {
112+
lineContent.containsComment = true
113+
} else {
114+
lineContent.containsCode = true
115+
}
116+
}
117+
}
118+
}
119+
}
120+
root.accept(visitor)
121+
val total = lineContents.size
122+
val code = lineContents.count { it.containsCode }
123+
val comment = lineContents.count { it.containsComment }
124+
tw.writeNumlines(id, total, code, comment)
125+
}
126+
127+
private class LineContent {
128+
var containsComment = false
129+
var containsCode = false
130+
}
131+
}

0 commit comments

Comments
 (0)