Mark rule disabling
This PR adds the possibility to exclude mark rules from being loaded. The disabled mark rules are specified as fully qualified name (`package.markRuleName`) either in the configuration file or as a CLI option. The asterisk (`*`) is used as a wildcard to disable an entire package.
MODIFY
src/main/java/de/fraunhofer/aisec/codyze/analysis/AnalysisServer.java
src/main/java/de/fraunhofer/aisec/codyze/analysis/AnalysisServer.java
@@ -3,6 +3,7 @@ package de.fraunhofer.aisec.codyze.analysis;
import de.fraunhofer.aisec.codyze.JythonInterpreter;
import de.fraunhofer.aisec.codyze.analysis.markevaluation.Evaluator;
+import de.fraunhofer.aisec.codyze.config.DisabledMarkRulesValue;
import de.fraunhofer.aisec.codyze.crymlin.builtin.Builtin;
import de.fraunhofer.aisec.codyze.crymlin.builtin.BuiltinRegistry;
import de.fraunhofer.aisec.codyze.crymlin.connectors.lsp.CpgLanguageServer;
@@ -232,7 +233,7 @@ public class AnalysisServer {
* Load MARK model as given in configuration, if it has not been set manually before.
*/
File[] markModelLocations = Arrays.stream(config.markModelFiles).map(File::new).toArray(File[]::new);
- loadMarkRules(markModelLocations);
+ loadMarkRules(config.packageToDisabledMarkRules, markModelLocations);
}
/**
@@ -265,6 +266,16 @@ public class AnalysisServer {
* @param markFiles load all mark entities/rules from these files
*/
public void loadMarkRules(@NonNull File... markFiles) {
+ loadMarkRules(new HashMap<>(), markFiles);
+ }
+
+ /**
+ * Loads all MARK rules from a file or a directory but skips files specified in packageToDisabledMarkRules map
+ *
+ * @param markFiles load all mark entities/rules from these files
+ * @param packageToDisabledMarkRules skip specified files during loading
+ */
+ public void loadMarkRules(Map<String, DisabledMarkRulesValue> packageToDisabledMarkRules, @NonNull File... markFiles) {
File markDescriptionFile = null;
XtextParser parser = new XtextParser();
@@ -312,7 +323,7 @@ public class AnalysisServer {
start = Instant.now();
log.info("Transforming MARK Xtext to internal format");
- this.markModel = new MarkModelLoader().load(markModels);
+ this.markModel = new MarkModelLoader().load(markModels, packageToDisabledMarkRules);
log.info(
"Done Transforming MARK Xtext to internal format in {} ms",
Duration.between(start, Instant.now()).toMillis());

MODIFY
src/main/java/de/fraunhofer/aisec/codyze/analysis/ServerConfiguration.java
src/main/java/de/fraunhofer/aisec/codyze/analysis/ServerConfiguration.java
package de.fraunhofer.aisec.codyze.analysis;
+import de.fraunhofer.aisec.codyze.config.DisabledMarkRulesValue;
import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend;
import kotlin.Pair;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.io.File;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
/** The configuration for the {@link AnalysisServer} holds all values used by the server. */
public class ServerConfiguration {
@@ -56,6 +59,9 @@ public class ServerConfiguration {
/** Additional registered languages */
public final List<Pair<Class<? extends LanguageFrontend>, List<String>>> additionalLanguages;
+ /** Disabled Mark rules. key=package, value=(disableEntirePackage, markRulesToDisable) */
+ public final Map<String, DisabledMarkRulesValue> packageToDisabledMarkRules;
+
private ServerConfiguration(
boolean launchConsole,
boolean launchLsp,
@@ -65,6 +71,7 @@ public class ServerConfiguration {
boolean useUnityBuild,
@NonNull File[] includePath,
boolean disableGoodFindings,
+ Map<String, DisabledMarkRulesValue> packageToDisabledMarkRules,
boolean pedantic,
List<Pair<Class<? extends LanguageFrontend>, List<String>>> additionalLanguages) {
this.launchConsole = launchConsole;
@@ -77,6 +84,7 @@ public class ServerConfiguration {
this.disableGoodFindings = disableGoodFindings;
this.pedantic = pedantic;
this.additionalLanguages = additionalLanguages;
+ this.packageToDisabledMarkRules = packageToDisabledMarkRules;
}
public static Builder builder() {
@@ -96,6 +104,7 @@ public class ServerConfiguration {
private boolean disableGoodFindings;
private boolean pedantic;
public final List<Pair<Class<? extends LanguageFrontend>, List<String>>> additionalLanguages = new ArrayList<>();
+ private Map<String, DisabledMarkRulesValue> packageToDisabledMarkRules = Collections.emptyMap();
public Builder launchConsole(boolean launchConsole) {
this.launchConsole = launchConsole;
@@ -156,6 +165,11 @@ public class ServerConfiguration {
return this;
}
+ public Builder disableMark(Map<String, DisabledMarkRulesValue> disabledMarkRules) {
+ this.packageToDisabledMarkRules = disabledMarkRules;
+ return this;
+ }
+
public ServerConfiguration build() {
return new ServerConfiguration(
launchConsole,
@@ -166,6 +180,7 @@ public class ServerConfiguration {
useUnityBuild,
includePath,
disableGoodFindings,
+ packageToDisabledMarkRules,
pedantic,
additionalLanguages);
}

MODIFY
src/main/java/de/fraunhofer/aisec/codyze/config/CodyzeConfiguration.kt
src/main/java/de/fraunhofer/aisec/codyze/config/CodyzeConfiguration.kt
@@ -69,7 +69,7 @@ class CodyzeConfiguration {
paramLabel = "<package.rule>",
description =
[
- "The specified mark rules will be excluded from being parsed and processed. The rule has to be specified by the package and the rule name."],
+ "The specified mark rules will be excluded from being parsed and processed. The rule has to be specified by its fully qualified name (package.rule). If there is no package name, specify rule as \".rule\". Use \'*\' to disable an entire package."],
split = ","
)
var disabledMarkRules: List<String> = emptyList()

MODIFY
src/main/java/de/fraunhofer/aisec/codyze/config/Configuration.kt
src/main/java/de/fraunhofer/aisec/codyze/config/Configuration.kt
@@ -105,6 +105,26 @@ class Configuration {
config.registerLanguage(golangFrontendClazz, extensions)
}
}
+
+ if (!codyze.pedantic) {
+ val disabledRulesMap = mutableMapOf<String, DisabledMarkRulesValue>()
+ for (mName in codyze.disabledMarkRules) {
+ val index = mName.lastIndexOf('.')
+ val packageName = mName.subSequence(0, index).toString()
+ val markName = mName.subSequence(index + 1, mName.length).toString()
+ if (markName.isNotEmpty()) {
+ disabledRulesMap.putIfAbsent(packageName, DisabledMarkRulesValue())
+ if (markName == "*")
+ disabledRulesMap.getValue(packageName).isDisablePackage = true
+ else disabledRulesMap[packageName]?.disabledMarkRuleNames?.add(markName)
+ } else
+ log.warn(
+ "Error while parsing disabled-mark-rules: \'$mName\' is not a valid name for a mark rule. Continue parsing disabled-mark-rules"
+ )
+ }
+ config.disableMark(disabledRulesMap)
+ }
+
return config.build()
}

ADD
src/main/java/de/fraunhofer/aisec/codyze/config/DisabledMarkRulesValue.kt
+package de.fraunhofer.aisec.codyze.config
+
+/**
+ * Stores whether the associated package should be disabled and the name of the rules to be disabled
+ */
+data class DisabledMarkRulesValue(
+ var isDisablePackage: Boolean = false,
+ val disabledMarkRuleNames: MutableSet<String> = mutableSetOf()
+)

MODIFY
src/main/java/de/fraunhofer/aisec/codyze/markmodel/MarkModelLoader.java
src/main/java/de/fraunhofer/aisec/codyze/markmodel/MarkModelLoader.java
package de.fraunhofer.aisec.codyze.markmodel;
import de.fraunhofer.aisec.codyze.analysis.markevaluation.ExpressionHelper;
+import de.fraunhofer.aisec.codyze.config.DisabledMarkRulesValue;
import de.fraunhofer.aisec.mark.markDsl.EntityDeclaration;
import de.fraunhofer.aisec.mark.markDsl.EntityStatement;
import de.fraunhofer.aisec.mark.markDsl.MarkModel;
@@ -15,11 +16,7 @@ import org.python.jline.internal.Log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
/**
* Parses a MarkModel provided by XText in the form of an ECore hierarchy into a simple (ECore-free) {@code Mark} model that the analysis server can work with.
@@ -31,7 +28,7 @@ public class MarkModelLoader {
private static final Logger log = LoggerFactory.getLogger(MarkModelLoader.class);
@NonNull
- public Mark load(Map<String, MarkModel> markModels, @Nullable String onlyfromthisfile) {
+ public Mark load(Map<String, MarkModel> markModels, Map<String, DisabledMarkRulesValue> packageToDisabledMarkRules, @Nullable String onlyfromthisfile) {
Mark m = new Mark();
for (Map.Entry<String, MarkModel> entry : markModels.entrySet()) {
@@ -59,14 +56,33 @@ public class MarkModelLoader {
continue;
}
MarkModel markModel = entry.getValue();
+
+ // For mark files without package the key is an empty string in the map
+ String key = markModel.getPackage() == null ? "" : markModel.getPackage().getName();
+
+ DisabledMarkRulesValue disabledRules = packageToDisabledMarkRules.getOrDefault(key,
+ new DisabledMarkRulesValue(false, Collections.emptySet()));
+
+ // check if entire package should be disabled
+ if (disabledRules.isDisablePackage()) {
+ log.info("Disabled all mark rules in package {}", markModel.getPackage().getName());
+ } else {
// Parse rules
for (RuleDeclaration r : markModel.getRule()) {
// todo @FW: should rules also have package names? what is the exact reasoning behind
// packages?
+
+ // check if rule should be disabled
+ if (disabledRules.getDisabledMarkRuleNames().contains(r.getName())) {
+ log.info("Disabled mark rule {} in package {}", r.getName(), markModel.getPackage() == null ? "" : markModel.getPackage().getName());
+ continue;
+ }
+
MRule rule = parseRule(r, m, entry.getKey());
m.getRules().add(rule);
}
}
+ }
/*
* VALIDATE todo validate MARK files. check e.g.: - Entities - one function call is not part of two ops (of one or two entities) if this would be the case, many
@@ -95,9 +111,19 @@ public class MarkModelLoader {
}
}
+ @NonNull
+ public Mark load(Map<String, MarkModel> markModels, Map<String, DisabledMarkRulesValue> packageToDisabledMarkRules) {
+ return load(markModels, packageToDisabledMarkRules, null);
+ }
+
+ @NonNull
+ public Mark load(Map<String, MarkModel> markModels, @Nullable String onlyfromthisfile) {
+ return load(markModels, Collections.emptyMap(), onlyfromthisfile);
+ }
+
@NonNull
public Mark load(Map<String, MarkModel> markModels) {
- return load(markModels, null);
+ return load(markModels, Collections.emptyMap(), null);
}
private MRule parseRule(RuleDeclaration rule, Mark mark, String containedInThisFile) {

ADD
src/test/java/de/fraunhofer/aisec/codyze/crymlin/MarkLoadTest.kt
+package de.fraunhofer.aisec.codyze.crymlin
+
+import de.fraunhofer.aisec.codyze.analysis.AnalysisServer
+import de.fraunhofer.aisec.codyze.config.DisabledMarkRulesValue
+import de.fraunhofer.aisec.codyze.markmodel.MRule
+import java.io.File
+import java.lang.Exception
+import kotlin.Throws
+import kotlin.test.*
+import org.junit.jupiter.api.*
+import org.junit.jupiter.api.Test
+
+internal class MarkLoadTest {
+
+ @Test
+ @Throws(Exception::class)
+ fun disabledRulesTest() {
+ val disabledMarkRules = mutableMapOf<String, DisabledMarkRulesValue>()
+ disabledMarkRules["java"] =
+ DisabledMarkRulesValue(
+ false,
+ mutableSetOf("UseValidAlgorithm", "RandomInitializationVector", "MockWhen2")
+ )
+ disabledMarkRules[""] = DisabledMarkRulesValue(false, mutableSetOf("SignatureOrder"))
+
+ analysisServer.loadMarkRules(
+ disabledMarkRules,
+ botanLocation,
+ javaLocation,
+ noPackageLocation
+ )
+ val actualMarkRules = analysisServer.markModel.rules
+ assertNotNull(actualMarkRules)
+ val actualMarkRuleNames = actualMarkRules.map { r -> r.name }
+
+ assertFalse(actualMarkRuleNames.isEmpty(), "Expected to contain mark rules but was empty")
+
+ val expectedSize =
+ allMarkRules.size -
+ disabledMarkRules.getOrDefault("java", DisabledMarkRulesValue())
+ .disabledMarkRuleNames
+ .size -
+ disabledMarkRules.getOrDefault("", DisabledMarkRulesValue())
+ .disabledMarkRuleNames
+ .size
+ assertEquals(
+ expectedSize,
+ actualMarkRuleNames.size,
+ "Expected size to be $expectedSize, but was ${actualMarkRuleNames.size}"
+ )
+
+ for (s in
+ disabledMarkRules.getOrDefault("java", DisabledMarkRulesValue())
+ .disabledMarkRuleNames) assertFalse(
+ actualMarkRuleNames.contains(s),
+ "Expected to have filtered out $s but was included in mark rules"
+ )
+
+ for (s in
+ disabledMarkRules.getOrDefault("", DisabledMarkRulesValue())
+ .disabledMarkRuleNames) assertFalse(
+ actualMarkRuleNames.contains(s),
+ "Expected to have filtered out $s but was included in mark rules"
+ )
+
+ for (s in botanMarkRuleNames) assertTrue(
+ actualMarkRuleNames.contains(s),
+ "Expected to have loaded $s but was not in mark rules"
+ )
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun disabledPackageTest() {
+ val disabledMarkRules = mutableMapOf<String, DisabledMarkRulesValue>()
+ disabledMarkRules["botan"] = DisabledMarkRulesValue(true, mutableSetOf())
+
+ analysisServer.loadMarkRules(
+ disabledMarkRules,
+ botanLocation,
+ javaLocation,
+ noPackageLocation
+ )
+ val actualMarkRules = analysisServer.markModel.rules
+ assertNotNull(actualMarkRules)
+ val actualMarkRuleNames = actualMarkRules.map { r -> r.name }
+
+ assertFalse(actualMarkRuleNames.isEmpty(), "Expected to contain mark rules but was empty")
+
+ val expectedSize = allMarkRules.size - botanMarkRuleNames.size
+ assertEquals(
+ expectedSize,
+ actualMarkRuleNames.size,
+ "Expected size to be $expectedSize, but was ${actualMarkRuleNames.size}"
+ )
+
+ for (s in botanMarkRuleNames) assertFalse(
+ actualMarkRuleNames.contains(s),
+ "Expected to have filtered out $s but was included in mark rules"
+ )
+
+ for (s in javaMarkRuleNames) assertTrue(
+ actualMarkRuleNames.contains(s),
+ "Expected to have loaded $s but was not in mark rules"
+ )
+
+ for (s in noPackageRuleNames) assertTrue(
+ actualMarkRuleNames.contains(s),
+ "Expected to have loaded $s but was not in mark rules"
+ )
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun disabledRulesAndPackageTest() {
+ val disabledMarkRules = mutableMapOf<String, DisabledMarkRulesValue>()
+ disabledMarkRules["java"] =
+ DisabledMarkRulesValue(
+ false,
+ mutableSetOf("UseValidAlgorithm", "RandomInitializationVector", "MockWhen2")
+ )
+ disabledMarkRules["botan"] = DisabledMarkRulesValue(true, mutableSetOf())
+
+ analysisServer.loadMarkRules(
+ disabledMarkRules,
+ botanLocation,
+ javaLocation,
+ noPackageLocation
+ )
+ val actualMarkRules = analysisServer.markModel.rules
+ assertNotNull(actualMarkRules)
+ val actualMarkRuleNames = actualMarkRules.map { r -> r.name }
+
+ assertFalse(actualMarkRuleNames.isEmpty(), "Expected to contain mark rules but was empty")
+
+ val expectedSize =
+ allMarkRules.size -
+ disabledMarkRules.getOrDefault("java", DisabledMarkRulesValue())
+ .disabledMarkRuleNames
+ .size -
+ botanMarkRuleNames.size
+ assertEquals(
+ expectedSize,
+ actualMarkRuleNames.size,
+ "Expected size to be $expectedSize, but was ${actualMarkRuleNames.size}"
+ )
+
+ for (s in
+ disabledMarkRules.getOrDefault("java", DisabledMarkRulesValue())
+ .disabledMarkRuleNames) assertFalse(
+ actualMarkRuleNames.contains(s),
+ "Expected to have filtered out $s but was included in mark rules"
+ )
+
+ for (s in botanMarkRuleNames) assertFalse(
+ actualMarkRuleNames.contains(s),
+ "Expected to have filtered out $s but was included in mark rules"
+ )
+
+ for (s in noPackageRuleNames) assertTrue(
+ actualMarkRuleNames.contains(s),
+ "Expected to have loaded $s but was not in mark rules"
+ )
+ }
+
+ companion object {
+ private lateinit var analysisServer: AnalysisServer
+ private lateinit var javaLocation: File
+ private lateinit var botanLocation: File
+ private lateinit var noPackageLocation: File
+ private lateinit var allMarkRules: MutableList<MRule>
+ private lateinit var botanMarkRuleNames: List<String>
+ private lateinit var javaMarkRuleNames: List<String>
+ private lateinit var noPackageRuleNames: List<String>
+
+ @BeforeAll
+ @JvmStatic
+ fun startup() {
+ analysisServer = AnalysisServer.builder().build()
+ assertNotNull(analysisServer)
+
+ val botanMarkResource =
+ MarkLoadTest::class.java.classLoader.getResource("real-examples/botan/MARK")
+ assertNotNull(botanMarkResource)
+ botanLocation = File(botanMarkResource.file)
+ assertNotNull(botanLocation)
+
+ val javaMarkResource =
+ MarkLoadTest::class.java.classLoader.getResource(
+ "real-examples/bc/rwedoff.Password-Manager"
+ )
+ assertNotNull(javaMarkResource)
+ javaLocation = File(javaMarkResource.file)
+ assertNotNull(javaLocation)
+
+ val noPackageResource =
+ MarkLoadTest::class.java.classLoader.getResource("unittests/nfa-test.mark")
+ assertNotNull(botanMarkResource)
+ noPackageLocation = File(noPackageResource.file)
+ assertNotNull(noPackageLocation)
+
+ analysisServer.loadMarkRules(botanLocation, javaLocation, noPackageLocation)
+ allMarkRules = analysisServer.markModel.rules
+ assertNotNull(allMarkRules)
+
+ analysisServer.loadMarkRules(botanLocation)
+ val botanMarkRules = analysisServer.markModel.rules
+ assertNotNull(botanMarkRules)
+ botanMarkRuleNames = botanMarkRules.map { r -> r.name }
+
+ analysisServer.loadMarkRules(javaLocation)
+ val javaMarkRules = analysisServer.markModel.rules
+ assertNotNull(javaMarkRules)
+ javaMarkRuleNames = javaMarkRules.map { r -> r.name }
+
+ analysisServer.loadMarkRules(noPackageLocation)
+ val noPackageMarkRules = analysisServer.markModel.rules
+ assertNotNull(noPackageMarkRules)
+ noPackageRuleNames = noPackageMarkRules.map { r -> r.name }
+ }
+ }
+}

MODIFY
src/test/java/de/fraunhofer/aisec/cpg/analysis/fsm/FSMBuilderTest.kt
src/test/java/de/fraunhofer/aisec/cpg/analysis/fsm/FSMBuilderTest.kt
@@ -24,7 +24,7 @@ class FSMBuilderTest {
val parser = XtextParser()
parser.addMarkFile(markPoC1)
val markModels = parser.parse()
- val mark = MarkModelLoader().load(markModels, null)
+ val mark = MarkModelLoader().load(markModels)
assertNotNull(mark)
var dfa = DFA()
for (rule in mark.rules) {
@@ -55,7 +55,7 @@ class FSMBuilderTest {
val parser = XtextParser()
parser.addMarkFile(markPoC1)
val markModels = parser.parse()
- val mark = MarkModelLoader().load(markModels, null)
+ val mark = MarkModelLoader().load(markModels)
assertNotNull(mark)
var dfa = DFA()
for (rule in mark.rules) {
@@ -97,7 +97,7 @@ class FSMBuilderTest {
val parser = XtextParser()
parser.addMarkFile(markPoC1)
val markModels = parser.parse()
- val mark = MarkModelLoader().load(markModels, null)
+ val mark = MarkModelLoader().load(markModels)
assertNotNull(mark)
var dfa = DFA()
for (rule in mark.rules) {
@@ -143,7 +143,7 @@ class FSMBuilderTest {
val parser = XtextParser()
parser.addMarkFile(markPoC1)
val markModels = parser.parse()
- val mark = MarkModelLoader().load(markModels, null)
+ val mark = MarkModelLoader().load(markModels)
assertNotNull(mark)
for (rule in mark.rules) {
if (rule.statement.ensure.exp is OrderExpression) {

Mark rule disabling (#426)

This PR adds the possibility to exclude mark rules from being loaded. The disabled mark rules are specified as fully qualified name (`package.markRuleName`) either in the configuration file or as a CLI option. The asterisk (`*`) is used as a wildcard to disable an entire package.