/
CodyzeDfaOrderEvaluator.kt
142 lines (124 loc) · 5.81 KB
/
CodyzeDfaOrderEvaluator.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/*
* Copyright (c) 2022, Fraunhofer AISEC. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.fraunhofer.aisec.codyze.backends.cpg.coko.ordering
import de.fraunhofer.aisec.codyze.backends.cpg.coko.CpgFinding
import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.EvaluationContext
import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.Finding
import de.fraunhofer.aisec.codyze.specificationLanguages.coko.core.dsl.Rule
import de.fraunhofer.aisec.cpg.analysis.fsm.DFA
import de.fraunhofer.aisec.cpg.analysis.fsm.DFAOrderEvaluator
import de.fraunhofer.aisec.cpg.analysis.fsm.Edge
import de.fraunhofer.aisec.cpg.graph.Node
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlin.reflect.full.findAnnotation
val logger = KotlinLogging.logger {}
/**
* Codyze-specific implementation of the [DFAOrderEvaluator]. Its main purpose is to collect the
* findings in case of violations to the order.
*/
@Suppress("LongParameterList")
class CodyzeDfaOrderEvaluator(
val context: EvaluationContext,
val hashToMethod: Map<String, String>,
dfa: DFA,
consideredBases: Set<Node>,
nodeToRelevantMethod: Map<Node, Set<String>>,
thisPositionOfNode: Map<Node, Int> = mapOf(), // for non-object oriented languages
consideredResetNodes: Set<Node>,
eliminateUnreachableCode: Boolean = true
) : DFAOrderEvaluator(
dfa,
consideredBases,
nodeToRelevantMethod,
consideredResetNodes,
thisPositionOfNode,
eliminateUnreachableCode
) {
val findings: MutableSet<CpgFinding> = mutableSetOf()
private val userDefinedFailMessage = context.rule.findAnnotation<Rule>()?.failMessage
.takeIf { it?.isEmpty() == false }
private val userDefinedPassMessage = context.rule.findAnnotation<Rule>()?.passMessage
.takeIf { it?.isEmpty() == false }
@Suppress("UnsafeCallOnNullableType")
private fun getPossibleNextEdges(edges: Set<Edge>?) = edges?.mapNotNull { hashToMethod[it.op] }?.sorted()
private fun getMethods(node: Node): Set<String> =
nodeToRelevantMethod.getOrDefault(node, emptySet()).mapNotNull { hashToMethod[it] }.toSet()
/**
* Collects a finding if the [node] makes an operation which violates the desired order.
*/
override fun actionMissingTransitionForNode(node: Node, fsm: DFA, interproceduralFlow: Boolean) {
val possibleNextEdges = getPossibleNextEdges(fsm.currentState?.outgoingEdges)
var defaultMessage =
"\"${node.code}\". Op \"${getMethods(node)}\" is not allowed. " +
"Expected one of: " + possibleNextEdges?.joinToString(", ")
if (possibleNextEdges?.isEmpty() == true && fsm.currentState?.isAcceptingState == true) {
defaultMessage = "\"${node.code}\". " +
"Op \"${getMethods(node)}\" is not allowed. No other calls are allowed on this base."
}
val message = userDefinedFailMessage ?: defaultMessage
findings.add(
CpgFinding(
kind = Finding.Kind.Fail,
node = node,
message = "Violation against Order: $message",
relatedNodes = fsm.executionTrace.map { it.cpgNode }.filter { it != node }
)
)
}
/**
* Collects the finding in the AnalysisContext because the DFA finished analyzing the function
* but the [base] did not terminate in an accepting state (i.e., some operations are missing).
*/
override fun actionNonAcceptingTermination(base: String, fsm: DFA, interproceduralFlow: Boolean) {
if (fsm.executionTrace.size == 1) {
return // We have not really started yet, so no output here.
}
val baseDeclName = base.split("|")[1].split(".").first()
val node = fsm.executionTrace.last().cpgNode
val possibleNextEdges = getPossibleNextEdges(fsm.currentState?.outgoingEdges)
val defaultMessage = "Base $baseDeclName is not correctly terminated. " +
"Expected one of [${possibleNextEdges?.joinToString(", ")}] " +
"to follow the correct last call on this base."
val message = userDefinedFailMessage ?: defaultMessage
findings.add(
CpgFinding(
kind = Finding.Kind.Fail,
node = node,
message = "Violation against Order: $message",
relatedNodes = fsm.executionTrace.map { it.cpgNode }.filter { it != node }
)
)
}
/**
* Contains the functionality which is executed if the DFA terminated in an accepting state for
* the given [base]. This means that all required statements have been executed for [base] so
* far. The [fsm] holds the execution trace found by the analysis.
*/
override fun actionAcceptingTermination(base: String, fsm: DFA, interproceduralFlow: Boolean) {
val baseDeclName = base.split("|")[1].split(".").first()
val node = fsm.executionTrace.last().cpgNode
val defaultMessage = "$baseDeclName is used correctly."
val message = userDefinedPassMessage ?: defaultMessage
findings.add(
CpgFinding(
kind = Finding.Kind.Pass,
node = node,
message = "Order validated: $message",
relatedNodes = fsm.executionTrace.map { it.cpgNode }.filter { it != node }
)
)
}
}