Skip to content

Commit a7920a2

Browse files
authored
Merge pull request #22 from SpringKill-team/feature/register
feature: Problem register #5 #12
2 parents 7037047 + 7e95eab commit a7920a2

12 files changed

Lines changed: 470 additions & 40 deletions

File tree

.run/Run Plugin.run.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<option value="runIde" />
1515
</list>
1616
</option>
17-
<option name="vmOptions" value="" />
17+
<option name="vmOptions" value="-Xmx10240m" />
1818
</ExternalSystemSettings>
1919
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
2020
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>

src/main/kotlin/org/skgroup/securityinspector/actions/BuildCallGraphAction.kt

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@ package org.skgroup.securityinspector.actions
33
import com.intellij.openapi.actionSystem.AnAction
44
import com.intellij.openapi.actionSystem.AnActionEvent
55
import com.intellij.openapi.actionSystem.CommonDataKeys
6+
import com.intellij.openapi.application.ApplicationManager
7+
import com.intellij.openapi.application.readAction
68
import com.intellij.openapi.project.Project
79
import com.intellij.psi.PsiMethod
10+
import kotlinx.coroutines.CoroutineScope
11+
import kotlinx.coroutines.Dispatchers
12+
import kotlinx.coroutines.launch
813
import org.skgroup.securityinspector.ui.service.CallGraphGenerator
914
import org.skgroup.securityinspector.ui.service.SecurityInspectorProjectService
1015

@@ -25,9 +30,19 @@ class BuildCallGraphAction : AnAction() {
2530
}
2631

2732
override fun update(e: AnActionEvent) {
28-
val psiElement = e.getData(CommonDataKeys.PSI_ELEMENT)
29-
//当选中的是方法时,添加右键菜单
30-
e.presentation.isEnabledAndVisible = psiElement is PsiMethod
33+
// val project = e.project
34+
// val psiElement = e.getData(CommonDataKeys.PSI_ELEMENT)
35+
36+
CoroutineScope(Dispatchers.Default).launch {
37+
val isMethod = readAction {
38+
val psiElement = e.getData(CommonDataKeys.PSI_ELEMENT)
39+
psiElement is PsiMethod
40+
}
41+
42+
ApplicationManager.getApplication().invokeLater {
43+
e.presentation.isEnabledAndVisible = isMethod
44+
}
45+
}
3146
}
3247

3348
private fun buildCallGraphForMethod(project: Project?, method: PsiMethod) {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package org.skgroup.securityinspector.actions
2+
3+
import com.intellij.openapi.actionSystem.AnAction
4+
import com.intellij.openapi.actionSystem.AnActionEvent
5+
import com.intellij.openapi.actionSystem.CommonDataKeys
6+
import com.intellij.openapi.application.ApplicationManager
7+
import com.intellij.openapi.application.readAction
8+
import com.intellij.openapi.project.Project
9+
import com.intellij.psi.PsiMethod
10+
import kotlinx.coroutines.CoroutineScope
11+
import kotlinx.coroutines.Dispatchers
12+
import kotlinx.coroutines.launch
13+
import org.skgroup.securityinspector.ui.service.CallGraphSearcher
14+
import org.skgroup.securityinspector.ui.service.SecurityInspectorProjectService
15+
16+
/**
17+
* 类描述:SearchAsSinkAction 类用于增加一个右键菜单,直接显示调用链。
18+
*
19+
* @author springkill
20+
* @version 1.0
21+
* @since 2025/3/9
22+
*/
23+
class SearchAsSinkAction : AnAction() {
24+
override fun actionPerformed(e: AnActionEvent) {
25+
val project = e.getData(CommonDataKeys.PROJECT)
26+
val psiElement = e.getData(CommonDataKeys.PSI_ELEMENT)
27+
28+
if (psiElement is PsiMethod) {
29+
searchAsSink(project, psiElement)
30+
}
31+
}
32+
33+
override fun update(e: AnActionEvent) {
34+
// val project = e.project
35+
// val psiElement = e.getData(CommonDataKeys.PSI_ELEMENT)
36+
37+
CoroutineScope(Dispatchers.Default).launch {
38+
39+
val isMethod = readAction {
40+
val psiElement = e.getData(CommonDataKeys.PSI_ELEMENT)
41+
psiElement is PsiMethod
42+
}
43+
44+
ApplicationManager.getApplication().invokeLater {
45+
e.presentation.isEnabledAndVisible = isMethod
46+
}
47+
}
48+
}
49+
50+
private fun searchAsSink(project: Project?, method: PsiMethod) {
51+
val service = project?.getService(SecurityInspectorProjectService::class.java)
52+
53+
val mainToolWindow = service?.mainToolWindow ?: return
54+
val callGraphPanel = mainToolWindow.analysisPanel
55+
val uiComponents = callGraphPanel.uiComponents
56+
uiComponents.sinkField.text = method.name
57+
CallGraphSearcher.search(
58+
uiComponents.sourceField,
59+
uiComponents.sinkField,
60+
uiComponents.infoArea,
61+
uiComponents.searchResultRootNode,
62+
uiComponents.searchResultTreeModel,
63+
project
64+
)
65+
}
66+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.skgroup.securityinspector.analysis.ast
2+
3+
import com.intellij.openapi.vfs.VirtualFile
4+
import org.skgroup.securityinspector.enums.SinkCallMode
5+
6+
/**
7+
* 类描述:ProjectIssue 类用于存储SinkFinder的跳转信息。
8+
*
9+
* @author springkill
10+
* @version 1.0
11+
*/
12+
data class ProjectIssue(
13+
val file: VirtualFile,
14+
val line: Int,
15+
val type: String,
16+
val subType: String,
17+
val callMode: SinkCallMode
18+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.skgroup.securityinspector.enums
2+
3+
/**
4+
* 枚举描述:SinkCallMode 枚举用于对不同的高亮显示进行判断。
5+
*
6+
* @author springkill
7+
* @version 1.0
8+
*/
9+
enum class SinkCallMode {
10+
SINGLE_SINK,
11+
HAS_CALL,
12+
REACH_SOURCE
13+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package org.skgroup.securityinspector.ui
2+
3+
import com.intellij.openapi.application.ApplicationManager
4+
import com.intellij.openapi.fileEditor.OpenFileDescriptor
5+
import com.intellij.openapi.progress.ProgressIndicator
6+
import com.intellij.openapi.progress.ProgressManager
7+
import com.intellij.openapi.progress.Task
8+
import com.intellij.openapi.project.Project
9+
import com.intellij.openapi.vfs.VirtualFile
10+
import com.intellij.openapi.wm.ToolWindow
11+
import com.intellij.openapi.wm.ToolWindowFactory
12+
import com.intellij.ui.components.JBScrollPane
13+
import com.intellij.ui.content.ContentFactory
14+
import com.intellij.ui.table.JBTable
15+
import org.skgroup.securityinspector.ui.component.FirstColumnRenderer
16+
import org.skgroup.securityinspector.ui.component.HighlightRenderer
17+
import org.skgroup.securityinspector.utils.GraphUtils.collectProjectIssues
18+
import java.awt.BorderLayout
19+
import java.awt.event.MouseAdapter
20+
import java.awt.event.MouseEvent
21+
import javax.swing.JButton
22+
import javax.swing.JPanel
23+
import javax.swing.table.DefaultTableModel
24+
25+
/**
26+
* 类描述:IssueViewWindow 类用于创建SinkFinder。
27+
*
28+
* @author springkill
29+
* @version 1.0
30+
*/
31+
class IssueViewWindow : ToolWindowFactory {
32+
33+
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
34+
val panel = JPanel(BorderLayout())
35+
val tableModel = DefaultTableModel(arrayOf("File", "Line", "Type", "SubType", "CallMode"), 0)
36+
val refreshButton = JButton("Init Sink")
37+
val table = object : JBTable(tableModel) {
38+
override fun isCellEditable(row: Int, column: Int): Boolean {
39+
return false
40+
}
41+
}
42+
43+
table.apply {
44+
columnModel.getColumn(0).cellRenderer = FirstColumnRenderer()
45+
columnModel.getColumn(4).cellRenderer = HighlightRenderer()
46+
table.setAutoCreateRowSorter(true)
47+
addMouseListener(object : MouseAdapter() {
48+
override fun mouseClicked(e: MouseEvent) {
49+
if (e.clickCount == 2) {
50+
val selectedRow = table.selectedRow
51+
if (selectedRow != -1) {
52+
53+
val file = tableModel.getValueAt(selectedRow, 0) as VirtualFile
54+
val line = tableModel.getValueAt(selectedRow, 1).toString().toInt()
55+
56+
OpenFileDescriptor(project, file, line, 0).navigate(true)
57+
}
58+
}
59+
}
60+
})
61+
}
62+
63+
refreshButton.apply {
64+
addActionListener {
65+
ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Finding sink method", true) {
66+
override fun run(indicator: ProgressIndicator) {
67+
collectProjectIssues(project, callback = { issues ->
68+
69+
ApplicationManager.getApplication().invokeLater {
70+
tableModel.rowCount = 0
71+
issues.forEach { issue ->
72+
tableModel.addRow(arrayOf(issue.file, issue.line.toString(), issue.type, issue.subType, issue.callMode))
73+
}
74+
}
75+
})
76+
}
77+
})
78+
}
79+
}
80+
81+
panel.add(JBScrollPane(table), BorderLayout.CENTER)
82+
panel.add(refreshButton, BorderLayout.SOUTH)
83+
84+
val contentFactory = ContentFactory.getInstance()
85+
val content = contentFactory.createContent(panel, "", false)
86+
toolWindow.contentManager.addContent(content)
87+
}
88+
89+
}

src/main/kotlin/org/skgroup/securityinspector/ui/component/CallGraphUIComponents.kt

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.skgroup.securityinspector.ui.component
33
import com.intellij.openapi.fileEditor.OpenFileDescriptor
44
import com.intellij.openapi.project.Project
55
import com.intellij.openapi.ui.ComboBox
6+
import com.intellij.ui.Gray
67
import com.intellij.ui.JBSplitter
78
import com.intellij.ui.components.*
89
import com.intellij.ui.treeStructure.Tree
@@ -52,7 +53,7 @@ class CallGraphUIComponents(val project: Project) {
5253

5354
private fun createProgressBar() = JProgressBar().apply {
5455
isStringPainted = true
55-
background = Color(240, 240, 240)
56+
background = Gray._240
5657
font = Font("SansSerif", Font.BOLD, 12)
5758
border = BorderFactory.createEmptyBorder(2, 10, 2, 10)
5859
preferredSize = Dimension(300, 20)
@@ -187,14 +188,6 @@ class CallGraphUIComponents(val project: Project) {
187188
// 假设这里存了一个 MethodNode或字符串
188189
val userObject = node.userObject
189190

190-
// 如果 userObject 是个 MethodNode,就取 node.name
191-
// 如果只是字符串,就直接复制它
192-
val textToCopy = when (userObject) {
193-
is MethodNode -> userObject.name
194-
is String -> userObject
195-
else -> return
196-
}
197-
198191
if(userObject is MethodNode){
199192
val offset = userObject.sourceSpan
200193
// TODO 优化跳转到源码/Class的位置
@@ -204,14 +197,6 @@ class CallGraphUIComponents(val project: Project) {
204197
}
205198
}
206199

207-
// 把 textToCopy 写进剪贴板
208-
val clipboard = Toolkit.getDefaultToolkit().systemClipboard
209-
val selection = java.awt.datatransfer.StringSelection(textToCopy)
210-
clipboard.setContents(selection, selection)
211-
212-
// 如果想给个提示,可以在这里加日志或弹窗
213-
infoArea.append("Copied $textToCopy\n")
214-
// Messages.showInfoMessage("Copied $textToCopy", "Clipboard")
215200
}
216201
}
217202
})
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.skgroup.securityinspector.ui.component
2+
3+
import com.intellij.openapi.vfs.VirtualFile
4+
import java.awt.Component
5+
import javax.swing.JTable
6+
import javax.swing.table.DefaultTableCellRenderer
7+
8+
/**
9+
* 类描述:FirstColumnRenderer 类用于创建一个渲染器,渲染sinkfinder第一列的内容。
10+
*
11+
* @author springkill
12+
* @version 1.0
13+
*/
14+
class FirstColumnRenderer : DefaultTableCellRenderer() {
15+
override fun getTableCellRendererComponent(
16+
table: JTable?, value: Any?, isSelected: Boolean, hasFocus: Boolean, row: Int, column: Int
17+
): Component {
18+
val component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column)
19+
20+
if (column == 0) {
21+
val file = value as? VirtualFile
22+
if (file != null) {
23+
text = file.name
24+
}
25+
}
26+
27+
return component
28+
}
29+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.skgroup.securityinspector.ui.component
2+
3+
import com.intellij.ui.JBColor
4+
import org.skgroup.securityinspector.enums.SinkCallMode
5+
import org.skgroup.securityinspector.enums.SinkCallMode.*
6+
import java.awt.Color
7+
import java.awt.Component
8+
import javax.swing.JTable
9+
import javax.swing.table.DefaultTableCellRenderer
10+
import javax.swing.table.DefaultTableModel
11+
12+
/**
13+
* 类描述:HighlightRenderer 类用于创建一个渲染器,用于高亮显示。
14+
*
15+
* @author springkill
16+
* @version 1.0
17+
*/
18+
class HighlightRenderer : DefaultTableCellRenderer() {
19+
override fun getTableCellRendererComponent(
20+
table: JTable?, value: Any?, isSelected: Boolean, hasFocus: Boolean, row: Int, column: Int
21+
): Component {
22+
val component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column)
23+
24+
// 获取当前行的数据
25+
val model = table?.model as? DefaultTableModel
26+
val rowData = model?.getValueAt(row, column) as? SinkCallMode
27+
28+
when (rowData) {
29+
SINGLE_SINK -> {
30+
background = JBColor(Color(204, 255, 204), Color(102, 153, 102))
31+
foreground = JBColor.BLACK
32+
}
33+
REACH_SOURCE -> {
34+
background = JBColor(Color(255, 204, 204), Color(232, 108, 108))
35+
foreground = JBColor.BLACK
36+
}
37+
HAS_CALL -> {
38+
background = JBColor(Color(225, 229, 160), Color(240, 255, 96))
39+
foreground = JBColor.BLACK
40+
}
41+
null -> {
42+
background = table?.background ?: JBColor.WHITE
43+
foreground = table?.foreground ?: JBColor.BLACK
44+
}
45+
}
46+
47+
return component
48+
}
49+
}

0 commit comments

Comments
 (0)