Repository: avast/yaramod (language: python)

Issue:
Adding a new meta can add it to wrong place in the TokenStream
Consider following rule:
```
rule rule_with_metas {
    meta:
        int_meta = 42 // comment
    condition:
        true
}
```
After a new meta is added with
```
rule.add_meta('new_meta', yaramod.Literal(True))
```
it ends up before the comment and not right after it:

```
rule rule_with_metas {
    meta:
        int_meta = 42
        new_meta = true // comment
    condition:
        true
}
```


Discussion / hints:
Can use the following test:
```
def test_add_meta(self):
        yara_file = yaramod.Yaramod().parse_string('''
rule rule_with_metas {
    meta:
        str_meta = "string meta"
        int_meta = 42
        bool_meta = true // comment
    condition:
        true
}''')

        self.assertEqual(len(yara_file.rules), 1)

        rule = yara_file.rules[0]
        self.assertEqual(rule.name, 'rule_with_metas')
        self.assertEqual(rule.modifier, yaramod.RuleModifier.Empty)
        self.assertEqual(len(rule.metas), 3)
        rule.add_meta('new_meta', yaramod.Literal(False))
        self.assertEqual(len(rule.metas), 4)

        self.assertEqual(rule.metas[0].key, 'str_meta')
        self.assertEqual(rule.metas[1].key, 'int_meta')
        self.assertEqual(rule.metas[2].key, 'bool_meta')
        self.assertEqual(rule.metas[3].key, 'new_meta')
        self.assertTrue(rule.metas[3].value.is_bool)
        self.assertEqual(rule.metas[3].value.text, 'false')

        expected = r'''
rule rule_with_metas
{
	meta:
		str_meta = "string meta"
		int_meta = 42
		bool_meta = true // comment
		new_meta = false
	condition:
		true
}
'''
```

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9063dbf3..838e3fe4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,6 @@
 # Changelog
 
+* Fix adding meta to add it after comment of last present meta if present ([#102](https://github.com/avast/yaramod/issues/102), [#188](https://github.com/avast/yaramod/pull/188))
 * Separate includes and imports with blank lines ([#187](https://github.com/avast/yaramod/pull/187), [#130](https://github.com/avast/yaramod/issues/130))
 
 # v3.11.0 (2021-08-30)
diff --git a/src/types/rule.cpp b/src/types/rule.cpp
index f64e67cc..f76ed32d 100644
--- a/src/types/rule.cpp
+++ b/src/types/rule.cpp
@@ -472,7 +472,15 @@ void Rule::addMeta(const std::string& name, const Literal& value)
 	else
 	{
 		insert_before = _metas.back().getValueTokenIt();
-		++insert_before;
+		while (
+			insert_before->getType() != TokenType::NEW_LINE &&
+			insert_before->getType() != TokenType::STRINGS &&
+			insert_before->getType() != TokenType::CONDITION &&
+			insert_before->getType() != TokenType::RULE_END &&
+			insert_before != _tokenStream->end()
+		) {
+			++insert_before;
+		}
 	}
 	_tokenStream->emplace(insert_before, TokenType::NEW_LINE, _tokenStream->getNewLineStyle());
 	auto itKey = _tokenStream->emplace(insert_before, TokenType::META_KEY, name);


Test patch:
diff --git a/tests/cpp/yaramod_tests.cpp b/tests/cpp/yaramod_tests.cpp
index 04f4c5f7..a4ae7c8d 100644
--- a/tests/cpp/yaramod_tests.cpp
+++ b/tests/cpp/yaramod_tests.cpp
@@ -152,6 +152,36 @@ rule rule_with_added_metas
 )", yarafile->getTextFormatted());
 }
 
+TEST_F(YaramodTests,
+AddMetaToRulesWithComments) {
+	yaramod::Yaramod ymod;
+
+	std::stringstream input;
+	input << R"(
+	rule rule_with_commented_meta {
+		meta:
+			author = "Mr. Avastien" // added by Service
+		condition:
+			true
+	})";
+	auto yarafile = ymod.parseStream(input);
+
+	ASSERT_EQ(1u, yarafile->getRules().size());
+	const auto& rule = yarafile->getRules()[0];
+
+	rule->addMeta("bool_meta", Literal(false));
+	EXPECT_EQ(R"(
+rule rule_with_commented_meta
+{
+	meta:
+		author = "Mr. Avastien" // added by Service
+		bool_meta = false
+	condition:
+		true
+}
+)", yarafile->getTextFormatted());
+}
+
 TEST_F(YaramodTests,
 SetMeta) {
 	yaramod::Yaramod ymod;