You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

173 lines
6.7 KiB

  1. #include "UserScript/parser.h"
  2. #include <catch2/catch_test_macros.hpp>
  3. #include <ranges>
  4. #include <iostream>
  5. #include <fstream>
  6. #include <sstream>
  7. #include <random>
  8. #include <algorithm>
  9. TEST_CASE("Can parse") {
  10. std::string code = "/salad (/potato) 12 13 \"hello\" ident\n"
  11. "/salad 12 13 \"hello\" ident\n"
  12. "if(/test)\n"
  13. " /nice\n"
  14. "endif";
  15. std::vector<scripting::script_error> errors;
  16. auto lexed = scripting::ast::lex(code, errors);
  17. auto parsed = scripting::ast::parse(lexed, errors);
  18. if(not errors.empty()) {
  19. for(auto& line : errors) {
  20. std::cout << line.message << "\n at line " << line.location->line_number << ":" << line.location->column_number << "\n";
  21. std::cout << " " << *line.location->line_contents << "\n";
  22. std::cout << " " << std::string(line.location->column_number - 1, ' ') << "^\n";
  23. }
  24. }
  25. auto& block = parsed;
  26. REQUIRE(block.contents.size() == 3);
  27. REQUIRE(std::holds_alternative<std::unique_ptr<scripting::ast::command_expression>>(block.contents.front().contents));
  28. auto& cmd1 = std::get<std::unique_ptr<scripting::ast::command_expression>>(block.contents.front().contents);
  29. REQUIRE(cmd1->name.value == "salad");
  30. REQUIRE(std::holds_alternative<std::unique_ptr<scripting::ast::command_expression>>(std::span(block.contents).subspan(1).front().contents));
  31. auto& cmd2 = std::get<std::unique_ptr<scripting::ast::command_expression>>(std::span(block.contents).subspan(1).front().contents);
  32. REQUIRE(cmd2->name.value == "salad");
  33. REQUIRE(std::holds_alternative<std::unique_ptr<scripting::ast::conditional>>(std::span(block.contents).subspan(2).front().contents));
  34. auto& conditional = std::get<std::unique_ptr<scripting::ast::conditional>>(std::span(block.contents).subspan(2).front().contents);
  35. REQUIRE(std::holds_alternative<std::unique_ptr<scripting::ast::paren_expression>>(conditional->condition->contents));
  36. auto& paren = std::get<std::unique_ptr<scripting::ast::paren_expression>>(conditional->condition->contents)->content;
  37. REQUIRE(std::holds_alternative<std::unique_ptr<scripting::ast::command_expression>>(paren));
  38. auto& condition = std::get<std::unique_ptr<scripting::ast::command_expression>>(paren);
  39. REQUIRE(condition->name.value == "test");
  40. }
  41. TEST_CASE("Can parse 2") {
  42. std::string code = "/salad (/potato) 12+13*16/(/potato)+myvar \"hello\" ident\n"
  43. "/salad 12 13 \"hello\" ident\n"
  44. "if !(/test)\n"
  45. " /nice\n"
  46. "endif";
  47. std::vector<scripting::script_error> errors;
  48. auto lexed = scripting::ast::lex(code, errors);
  49. auto parsed = scripting::ast::parse(lexed, errors);
  50. if(not errors.empty()) {
  51. for(auto& line : errors) {
  52. std::cout << line.message << "\n at line " << line.location->line_number << ":" << line.location->column_number << "\n";
  53. std::cout << " " << *line.location->line_contents << "\n";
  54. std::cout << " " << std::string(line.location->column_number - 1, ' ') << "^\n";
  55. }
  56. }
  57. auto& block = parsed;
  58. REQUIRE(block.contents.size() == 3);
  59. REQUIRE(std::holds_alternative<std::unique_ptr<scripting::ast::command_expression>>(block.contents.front().contents));
  60. auto& cmd1 = std::get<std::unique_ptr<scripting::ast::command_expression>>(block.contents.front().contents);
  61. REQUIRE(cmd1->name.value == "salad");
  62. REQUIRE(cmd1->arguments.size() == 4);
  63. REQUIRE(std::holds_alternative<std::unique_ptr<scripting::ast::command_expression>>(std::span(block.contents).subspan(1).front().contents));
  64. auto& cmd2 = std::get<std::unique_ptr<scripting::ast::command_expression>>(std::span(block.contents).subspan(1).front().contents);
  65. REQUIRE(cmd2->name.value == "salad");
  66. REQUIRE(std::holds_alternative<std::unique_ptr<scripting::ast::conditional>>(std::span(block.contents).subspan(2).front().contents));
  67. auto& conditional = std::get<std::unique_ptr<scripting::ast::conditional>>(std::span(block.contents).subspan(2).front().contents);
  68. REQUIRE(std::holds_alternative<std::unique_ptr<scripting::ast::unary_algebraic_expression>>(conditional->condition->contents));
  69. }
  70. template<auto seed_template = -1>
  71. constexpr auto runner = [](){
  72. std::vector<std::string> sources = {
  73. "../tests/scripts/001.script",
  74. "../tests/scripts/002.script",
  75. "../tests/scripts/003.script",
  76. "../tests/scripts/004.script",
  77. "../tests/scripts/005.script",
  78. "../tests/scripts/006.script",
  79. "../tests/scripts/007.script",
  80. "../tests/scripts/008.script",
  81. };
  82. auto seed = seed_template == -1 ? std::random_device{}() : seed_template;
  83. std::cout << "TEST \"Try to crash the parser 1\" with seed " << seed << std::endl;
  84. std::mt19937_64 rand(seed);
  85. auto mod = [&](std::string tmp) -> std::string {
  86. if(tmp.empty()) return tmp;
  87. auto alter_idx = rand()%tmp.size();
  88. switch(rand()%3) {
  89. case 0:{
  90. tmp.erase(alter_idx);
  91. }break;
  92. case 1:{
  93. tmp[alter_idx] = rand() % 256;
  94. }break;
  95. case 2:{
  96. tmp.insert(alter_idx, 1, char(rand() % 256));
  97. }break;
  98. }
  99. return tmp;
  100. };
  101. auto codes = sources | std::ranges::views::transform([](std::string file){
  102. std::ifstream file_str{file};
  103. std::stringstream read;
  104. read << file_str.rdbuf();
  105. return read.str();
  106. });
  107. std::vector<std::string> vec;
  108. std::copy(codes.begin(), codes.end(), std::back_inserter(vec));
  109. size_t count = 0;
  110. size_t error_cnt = 0;
  111. size_t success_cnt = 0;
  112. constexpr size_t max_count = 8000000;
  113. auto begin = std::chrono::high_resolution_clock::now();
  114. while(count < max_count) {
  115. std::cout << 100.0*double(count)/max_count <<"%"<< std::endl;
  116. for(auto& code : vec) {
  117. std::vector<scripting::script_error> errors;
  118. auto lexed = scripting::ast::lex(code, errors);
  119. auto parsed = scripting::ast::parse(lexed, errors);
  120. if(errors.empty()) success_cnt++;
  121. else error_cnt++;
  122. count++;
  123. }
  124. auto limit = std::min<size_t>(vec.size(), 5000) ;
  125. for(size_t idx = 0; idx < limit; ++idx) {
  126. vec.push_back(mod(vec[idx]));
  127. }
  128. std::transform(vec.begin(), vec.end(), vec.begin(), mod);
  129. std::shuffle(vec.begin(), vec.end(), rand);
  130. if(vec.size()>30000) vec.resize(30000);
  131. }
  132. auto end = std::chrono::high_resolution_clock::now();
  133. std::cout
  134. << "Successes: " << success_cnt << "\n"
  135. << "Failures: " << error_cnt << "\n"
  136. << "Ratio: " << double(success_cnt)/double(success_cnt+error_cnt) << "\n"
  137. << "Total time: " << std::chrono::duration_cast<std::chrono::microseconds>(end-begin).count() << "µs\n"
  138. << "Time per iteration: " << (std::chrono::duration_cast<std::chrono::nanoseconds>(end-begin)/(error_cnt+success_cnt)).count() << "ns\n";
  139. };
  140. TEST_CASE("Try to crash the parser (known seeds)") {
  141. runner<4138740281>();
  142. runner<1547293717>();
  143. runner<1759257947>();
  144. runner<2909912711>();
  145. runner<1236548620>();
  146. }
  147. TEST_CASE("Try to crash the parser (new seeds)") {
  148. runner<>();
  149. }