The Battle for Wesnoth  1.15.0+dev
schema_validator.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2011 - 2018 by Sytyi Nick <nsytyi@gmail.com>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
16 
17 #include "filesystem.hpp"
18 #include "gettext.hpp"
19 #include "log.hpp"
22 #include "wml_exception.hpp"
23 #include <tuple>
24 
25 namespace schema_validation
26 {
27 static lg::log_domain log_validation("validation");
28 
29 #define ERR_VL LOG_STREAM(err, log_validation)
30 #define WRN_VL LOG_STREAM(warn, log_validation)
31 #define LOG_VL LOG_STREAM(info, log_validation)
32 
33 static std::string at(const std::string& file, int line)
34 {
35  std::ostringstream ss;
36  ss << line << " " << file;
37  return "at " + ::lineno_string(ss.str());
38 }
39 
40 static void print_output(const std::string& message, bool flag_exception = false)
41 {
42 #ifndef VALIDATION_ERRORS_LOG
43  if(flag_exception) {
44  throw wml_exception("Validation error occurred", message);
45  } else {
46  ERR_VL << message;
47  }
48 #else
49  // dirty hack to avoid "unused" error in case of compiling with definition on
50  flag_exception = true;
51  if(flag_exception) {
52  ERR_VL << message;
53  }
54 #endif
55 }
56 
57 static void extra_tag_error(const std::string& file,
58  int line,
59  const std::string& name,
60  int n,
61  const std::string& parent,
62  bool flag_exception)
63 {
64  std::ostringstream ss;
65  ss << "Extra tag [" << name << "]; there may only be " << n << " [" << name << "] in [" << parent << "]\n"
66  << at(file, line) << "\n";
67  print_output(ss.str(), flag_exception);
68 }
69 
70 static void wrong_tag_error(
71  const std::string& file, int line, const std::string& name, const std::string& parent, bool flag_exception)
72 {
73  std::ostringstream ss;
74  ss << "Tag [" << name << "] may not be used in [" << parent << "]\n" << at(file, line) << "\n";
75  print_output(ss.str(), flag_exception);
76 }
77 
78 static void missing_tag_error(const std::string& file,
79  int line,
80  const std::string& name,
81  int n,
82  const std::string& parent,
83  bool flag_exception)
84 {
85  std::ostringstream ss;
86  ss << "Missing tag [" << name << "]; there must be " << n << " [" << name << "]s in [" << parent << "]\n"
87  << at(file, line) << "\n";
88  print_output(ss.str(), flag_exception);
89 }
90 
91 static void extra_key_error(
92  const std::string& file, int line, const std::string& tag, const std::string& key, bool flag_exception)
93 {
94  std::ostringstream ss;
95  ss << "Invalid key '" << key << "=' in tag [" << tag << "]\n" << at(file, line) << "\n";
96  print_output(ss.str(), flag_exception);
97 }
98 
99 static void missing_key_error(
100  const std::string& file, int line, const std::string& tag, const std::string& key, bool flag_exception)
101 {
102  std::ostringstream ss;
103  ss << "Missing key '" << key << "=' in tag [" << tag << "]\n" << at(file, line) << "\n";
104  print_output(ss.str(), flag_exception);
105 }
106 
107 static void wrong_value_error(const std::string& file,
108  int line,
109  const std::string& tag,
110  const std::string& key,
111  const std::string& value,
112  bool flag_exception)
113 {
114  std::ostringstream ss;
115  ss << "Invalid value '" << value << "' in key '" << key << "=' in tag [" << tag << "]\n" << at(file, line) << "\n";
116  print_output(ss.str(), flag_exception);
117 }
118 
119 static void wrong_path_error(const std::string& file,
120  int line,
121  const std::string& tag,
122  const std::string& key,
123  const std::string& value,
124  bool flag_exception)
125 {
126  std::ostringstream ss;
127  ss << "Unknown path reference '" << value << "' in key '" << key << "=' in tag [" << tag << "]\n" << at(file, line) << "\n";
128  print_output(ss.str(), flag_exception);
129 }
130 
131 static void wrong_type_error(const std::string & file, int line,
132  const std::string & tag,
133  const std::string & key,
134  const std::string & type,
135  bool flag_exception)
136 {
137  std::ostringstream ss;
138  ss << "Invalid type '" << type << "' in key '" << key << "=' in tag [" << tag << "]\n" << at(file, line) << "\n";
139  print_output(ss.str(), flag_exception);
140 }
141 
143 {
144 }
145 
146 schema_validator::schema_validator(const std::string& config_file_name, bool validate_schema)
147  : abstract_validator(config_file_name)
148  , config_read_(false)
150  , validate_schema_(validate_schema)
151 {
152  if(!read_config_file(config_file_name)) {
153  ERR_VL << "Schema file " << config_file_name << " was not read." << std::endl;
154  throw abstract_validator::error("Schema file " + config_file_name + " was not read.\n");
155  } else {
156  stack_.push(&root_);
157  counter_.emplace();
158  cache_.emplace();
160  LOG_VL << "Schema file " << config_file_name << " was read.\n"
161  << "Validator initialized\n";
162  }
163 }
164 
165 bool schema_validator::read_config_file(const std::string& filename)
166 {
167  config cfg;
168  try {
169  std::unique_ptr<abstract_validator> validator;
170  if(validate_schema_) {
171  validator.reset(new schema_self_validator());
172  }
173  preproc_map preproc(game_config::config_cache::instance().get_preproc_map());
174  filesystem::scoped_istream stream = preprocess_file(filename, &preproc);
175  read(cfg, *stream, validator.get());
176  } catch(const config::error& e) {
177  ERR_VL << "Failed to read file " << filename << ":\n" << e.what() << "\n";
178  return false;
179  }
180 
181  for(const config& g : cfg.child_range("wml_schema")) {
182  for(const config& schema : g.child_range("tag")) {
183  if(schema["name"].str() == "root") {
184  //@NOTE Don't know, maybe merging of roots needed.
185  root_ = wml_tag(schema);
186  }
187  }
188  for(const config& type : g.child_range("type")) {
189  try {
190  types_[type["name"].str()] = wml_type::from_config(type);
191  } catch(const std::exception&) {
192  // Need to check all type values in schema-generator
193  }
194  }
195  }
196 
197  config_read_ = true;
198  return true;
199 }
200 
201 /*
202  * Please, @Note that there is some magic in pushing and poping to/from stacks.
203  * assume they all are on their place due to parser algorithm
204  * and validation logic
205  */
206 void schema_validator::open_tag(const std::string& name, const config& parent, int start_line, const std::string& file, bool addittion)
207 {
208  if(name.empty()) {
209  // Opened the root tag; nothing special to do here
210  } else if(!stack_.empty()) {
211  const wml_tag* tag = nullptr;
212 
213  if(stack_.top()) {
214  tag = active_tag().find_tag(name, root_, parent);
215 
216  if(!tag) {
217  wrong_tag_error(file, start_line, name, stack_.top()->get_name(), create_exceptions_);
218  } else {
219  if(!addittion) {
220  counter& cnt = counter_.top()[name];
221  ++cnt.cnt;
222  }
223  }
224  }
225 
226  stack_.push(tag);
227  } else {
228  stack_.push(nullptr);
229  }
230 
231  counter_.emplace();
232  cache_.emplace();
233 }
234 
236 {
237  stack_.pop();
238  counter_.pop();
239  // cache_ is cleared in another place.
240 }
241 
242 void schema_validator::validate(const config& cfg, const std::string& name, int start_line, const std::string& file)
243 {
244  // close previous errors and print them to output.
245  for(auto& m : cache_.top()) {
246  for(auto& list : m.second) {
247  print(list);
248  }
249  }
250 
251  cache_.pop();
252 
253  // clear cache
254  auto cache_it = cache_.top().find(&cfg);
255  if(cache_it != cache_.top().end()) {
256  cache_it->second.clear();
257  }
258 
259  // Please note that validating unknown tag keys the result will be false
260  // Checking all elements counters.
261  if(have_active_tag() && is_valid()) {
262  for(const auto& tag : active_tag().tags(cfg)) {
263  int cnt = counter_.top()[tag.first].cnt;
264 
265  if(tag.second.get_min() > cnt) {
266  queue_message(cfg, MISSING_TAG, file, start_line, tag.second.get_min(), tag.first, "", name);
267  continue;
268  }
269 
270  if(tag.second.get_max() < cnt) {
271  queue_message(cfg, EXTRA_TAG, file, start_line, tag.second.get_max(), tag.first, "", name);
272  }
273  }
274 
275  // Checking if all mandatory keys are present
276  for(const auto& key : active_tag().keys(cfg)) {
277  if(key.second.is_mandatory()) {
278  if(cfg.get(key.first) == nullptr) {
279  queue_message(cfg, MISSING_KEY, file, start_line, 0, name, key.first);
280  }
281  }
282  }
283  }
284 }
285 
287  const config& cfg, const std::string& name, const std::string& value, int start_line, const std::string& file)
288 {
289  if(have_active_tag() && !active_tag().get_name().empty() && is_valid()) {
290  // checking existing keys
291  const wml_key* key = active_tag().find_key(name, cfg);
292  if(key) {
293  bool matched = false;
294  for(auto& possible_type : utils::split(key->get_type())) {
295  if(auto type = find_type(possible_type)) {
296  if(type->matches(value, types_)) {
297  matched = true;
298  break;
299  }
300  }
301  }
302  if(!matched) {
303  queue_message(cfg, WRONG_VALUE, file, start_line, 0, active_tag().get_name(), name, value);
304  }
305  } else {
306  queue_message(cfg, EXTRA_KEY, file, start_line, 0, active_tag().get_name(), name);
307  }
308  }
309 }
310 
311 void schema_validator::queue_message(const config& cfg, message_type t, const std::string& file, int line, int n, const std::string& tag, const std::string& key, const std::string& value)
312 {
313  cache_.top()[&cfg].emplace_back(t, file, line, n, tag, key, value);
314 }
315 
317 {
318  assert(have_active_tag() && "Tried to get active tag name when there was none");
319  return *stack_.top();
320 }
321 
323 {
324  auto it = types_.find(type);
325  if(it == types_.end()) {
326  return nullptr;
327  }
328  return it->second;
329 }
330 
332 {
333  return !stack_.empty() && stack_.top();
334 }
335 
337  std::stack<const wml_tag*> temp = stack_;
338  std::deque<std::string> path;
339  while(!temp.empty()) {
340  path.push_front(temp.top()->get_name());
341  temp.pop();
342  }
343  if(path.front() == "root") {
344  path.pop_front();
345  }
346  return utils::join(path, "/");
347 }
348 
350 {
351  switch(el.type) {
352  case WRONG_TAG:
354  break;
355  case EXTRA_TAG:
356  extra_tag_error(el.file, el.line, el.tag, el.n, el.value, create_exceptions_);
357  break;
358  case MISSING_TAG:
359  missing_tag_error(el.file, el.line, el.tag, el.n, el.value, create_exceptions_);
360  break;
361  case EXTRA_KEY:
363  break;
364  case WRONG_VALUE:
366  break;
367  case MISSING_KEY:
369  break;
370  case WRONG_TYPE:
372  break;
373  case WRONG_PATH:
375  break;
376  }
377 }
378 
380  : schema_validator(filesystem::get_wml_location("schema/schema.cfg"), false)
381  , type_nesting_()
382  , condition_nesting_()
383 {}
384 
385 void schema_self_validator::open_tag(const std::string& name, const config& parent, int start_line, const std::string& file, bool addition)
386 {
387  schema_validator::open_tag(name, parent, start_line, file, addition);
388  if(name == "type") {
389  type_nesting_++;
390  }
391  if(condition_nesting_ == 0) {
392  if(name == "if" || name == "switch") {
393  condition_nesting_ = 1;
394  } else if(name == "tag") {
395  tag_stack_.emplace();
396  }
397  } else {
399  }
400 }
401 
403 {
404  if(have_active_tag()) {
405  auto tag_name = active_tag().get_name();
406  if(tag_name == "type") {
407  type_nesting_--;
408  } else if(condition_nesting_ == 0 && tag_name == "tag") {
409  tag_stack_.pop();
410  }
411  }
412  if(condition_nesting_ > 0) {
414  }
416 }
417 
419  std::vector<std::string> path = utils::split(ref.value_, '/');
420  std::string suffix = path.back();
421  path.pop_back();
422  while(!path.empty()) {
423  std::string prefix = utils::join(path, "/");
424  auto link = links_.find(prefix);
425  if(link != links_.end()) {
426  std::string new_path = link->second + "/" + suffix;
427  if(defined_tag_paths_.count(new_path) > 0) {
428  return true;
429  }
430  path = utils::split(new_path, '/');
431  suffix = path.back();
432  //suffix = link->second + "/" + suffix;
433  } else {
434  auto supers = derivations_.equal_range(prefix);
435  if(supers.first != supers.second) {
436  reference super_ref = ref;
437  for( ; supers.first != supers.second; ++supers.first) {
438  super_ref.value_ = supers.first->second + "/" + suffix;
439  if(tag_path_exists(cfg, super_ref)) {
440  return true;
441  }
442  }
443  }
444  std::string new_path = prefix + "/" + suffix;
445  if(defined_tag_paths_.count(new_path) > 0) {
446  return true;
447  }
448  suffix = path.back() + "/" + suffix;
449  }
450  path.pop_back();
451  }
452  return false;
453 }
454 
455 void schema_self_validator::validate(const config& cfg, const std::string& name, int start_line, const std::string& file)
456 {
457  if(type_nesting_ == 1 && name == "type") {
458  defined_types_.insert(cfg["name"]);
459  } else if(name == "wml_schema") {
460  using namespace std::placeholders;
461  std::vector<reference> missing_types = referenced_types_, missing_tags = referenced_tag_paths_;
462  // Remove all the known types
463  missing_types.erase(std::remove_if(missing_types.begin(), missing_types.end(), std::bind(&reference::match, _1, std::cref(defined_types_))), missing_types.end());
464  // Remove all the known tags. This is more complicated since links behave similar to a symbolic link.
465  // In other words, the presence of links means there may be more than one way to refer to a given tag.
466  // But that's not all! It's possible to refer to a tag through a derived tag even if it's actually defined in the base tag.
467  auto end = std::remove_if(missing_tags.begin(), missing_tags.end(), std::bind(&reference::match, _1, std::cref(defined_tag_paths_)));
468  missing_tags.erase(std::remove_if(missing_tags.begin(), end, std::bind(&schema_self_validator::tag_path_exists, this, std::ref(cfg), _1)), missing_tags.end());
469  std::sort(missing_types.begin(), missing_types.end());
470  std::sort(missing_tags.begin(), missing_tags.end());
471  static const config dummy;
472  for(auto& ref : missing_types) {
473  std::string tag_name;
474  if(ref.tag_ == "key") {
475  tag_name = "type";
476  } else {
477  tag_name = "link";
478  }
479  queue_message(dummy, WRONG_TYPE, ref.file_, ref.line_, 0, ref.tag_, tag_name, ref.value_);
480  }
481  for(auto& ref : missing_tags) {
482  std::string tag_name;
483  if(ref.tag_ == "tag") {
484  tag_name = "super";
485  } else if(ref.tag_ == "link") {
486  tag_name = "name";
487  }
488  queue_message(dummy, WRONG_PATH, ref.file_, ref.line_, 0, ref.tag_, tag_name, ref.value_);
489  }
490  }
491  schema_validator::validate(cfg, name, start_line, file);
492 }
493 
494 void schema_self_validator::validate_key(const config& cfg, const std::string& name, const std::string& value, int start_line, const std::string& file)
495 {
496  schema_validator::validate_key(cfg, name, value, start_line, file);
497  if(have_active_tag() && !active_tag().get_name().empty() && is_valid()) {
498  const std::string& tag_name = active_tag().get_name();
499  if(tag_name == "key" && name == "type" ) {
500  for(auto& possible_type : utils::split(cfg["type"])) {
501  referenced_types_.emplace_back(possible_type, file, start_line, tag_name);
502  }
503  } else if((tag_name == "type" || tag_name == "element") && name == "link") {
504  referenced_types_.emplace_back(cfg["link"], file, start_line, tag_name);
505  } else if(tag_name == "link" && name == "name") {
506  referenced_tag_paths_.emplace_back(cfg["name"], file, start_line, tag_name);
507  std::string link_name = utils::split(cfg["name"], '/').back();
508  links_.emplace(current_path() + "/" + link_name, cfg["name"]);
509  } else if(tag_name == "tag" && name == "super") {
510  for(auto super : utils::split(cfg["super"])) {
511  referenced_tag_paths_.emplace_back(super, file, start_line, tag_name);
512  derivations_.emplace(std::make_pair(current_path(), super));
513  }
514  } else if(condition_nesting_ == 0 && tag_name == "tag" && name == "name") {
515  tag_stack_.top() = value;
517  }
518  }
519 }
520 
522 {
523  std::stack<std::string> temp = tag_stack_;
524  std::deque<std::string> path;
525  while(!temp.empty()) {
526  path.push_front(temp.top());
527  temp.pop();
528  }
529  if(path.front() == "root") {
530  path.pop_front();
531  }
532  return utils::join(path, "/");
533 }
534 
536 {
537  return std::make_tuple(file_, line_) < std::make_tuple(other.file_, other.line_);
538 }
539 
540 bool schema_self_validator::reference::match(const std::set<std::string>& with)
541 {
542  return with.count(value_) > 0;
543 }
544 
546 {
547  // The problem is that the schema being validated is that of the schema!!!
548  return root.find_tag(value_, root, cfg) != nullptr;
549 }
550 
551 } // namespace schema_validation{
std::string lineno_string(const std::string &lineno)
std::stack< const wml_tag * > stack_
bool create_exceptions_
Controls the way to print errors.
static config_cache & instance()
Get reference to the singleton object.
bool match(const std::set< std::string > &with)
static void wrong_value_error(const std::string &file, int line, const std::string &tag, const std::string &key, const std::string &value, bool flag_exception)
cnt_stack counter_
Contains number of children.
static void wrong_tag_error(const std::string &file, int line, const std::string &name, const std::string &parent, bool flag_exception)
int dummy
Definition: lstrlib.cpp:1125
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
std::multimap< std::string, std::string > derivations_
virtual void open_tag(const std::string &name, const config &parent, int start_line=0, const std::string &file="", bool addition=false) override
Is called when parser opens tag.
bool tag_path_exists(const config &cfg, const reference &ref)
Add a special kind of assert to validate whether the input from WML doesn&#39;t contain any problems that...
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:39
child_itors child_range(config_key_type key)
Definition: config.cpp:362
const attribute_value * get(config_key_type key) const
Returns a pointer to the attribute with the given key or nullptr if it does not exist.
Definition: config.cpp:742
Stores information about tag.
Definition: tag.hpp:46
static void missing_tag_error(const std::string &file, int line, const std::string &name, int n, const std::string &parent, bool flag_exception)
wml_tag root_
Root of schema information.
std::vector< std::string > split(const std::string &val, const char c, const int flags)
Splits a (comma-)separated string into a vector of pieces.
bool config_read_
Shows, if validator is initialized with schema file.
const wml_key * find_key(const std::string &name, const config &match, bool ignore_super=false) const
Returns pointer to child key.
Definition: tag.cpp:103
boost::iterator_range< key_iterator > keys(const config &cfg_match) const
Definition: tag.hpp:278
One of the realizations of serialization/validator.hpp abstract validator.
static const char * name(const std::vector< SDL_Joystick *> &joysticks, const std::size_t index)
Definition: joystick.cpp:48
Used in parsing config file.
Definition: validator.hpp:35
static void extra_key_error(const std::string &file, int line, const std::string &tag, const std::string &key, bool flag_exception)
std::stack< message_map > cache_
Caches error messages.
static void missing_key_error(const std::string &file, int line, const std::string &tag, const std::string &key, bool flag_exception)
static std::string at(const std::string &file, int line)
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:625
wml_type::map types_
Type validators.
bool strict_validation_enabled
Definition: validator.cpp:19
const wml_tag * find_tag(const std::string &fullpath, const wml_tag &root, const config &match, bool ignore_super=false) const
Returns pointer to tag using full path to it.
Definition: tag.cpp:159
void queue_message(const config &cfg, message_type t, const std::string &file, int line=0, int n=0, const std::string &tag="", const std::string &key="", const std::string &value="")
bool can_find(const wml_tag &root, const config &cfg)
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:39
std::string path
Definition: game_config.cpp:39
static void print_output(const std::string &message, bool flag_exception=false)
const char * what() const noexcept
Definition: exceptions.hpp:37
const std::string & get_type() const
Definition: key.hpp:63
#define LOG_VL
schema_validator(const std::string &filename, bool validate_schema=false)
Initializes validator from file.
Helper class, don&#39;t construct this directly.
static std::shared_ptr< wml_type > from_config(const config &cfg)
Definition: type.cpp:40
std::string get_wml_location(const std::string &filename, const std::string &current_dir)
Returns a complete path to the actual WML file or directory or an empty string if the file isn&#39;t pres...
wml_key is used to save the information about one key.
Definition: key.hpp:35
bool read_config_file(const std::string &filename)
Reads config from input.
double g
Definition: astarsearch.cpp:64
virtual void close_tag() override
As far as parser is built on stack, some realizations can store stack too.
std::shared_ptr< wml_type > ptr
Definition: type.hpp:40
Declarations for File-IO.
static int sort(lua_State *L)
Definition: ltablib.cpp:411
void expand_all(wml_tag &root)
Calls the expansion on each child.
Definition: tag.cpp:237
double t
Definition: astarsearch.cpp:64
static void wrong_path_error(const std::string &file, int line, const std::string &tag, const std::string &key, const std::string &value, bool flag_exception)
virtual void validate(const config &cfg, const std::string &name, int start_line, const std::string &file) override
Validates config.
const std::string & get_name() const
Definition: tag.hpp:157
virtual void open_tag(const std::string &name, const config &parent, int start_line=0, const std::string &file="", bool addition=false) override
Is called when parser opens tag.
Standard logging facilities (interface).
virtual void close_tag() override
As far as parser is built on stack, some realizations can store stack too.
std::map< std::string, struct preproc_define > preproc_map
#define e
Realization of serialization/validator.hpp abstract validator.
virtual void validate_key(const config &cfg, const std::string &name, const std::string &value, int start_line, const std::string &file) override
Checks if key is allowed and if its value is valid What exactly is validated depends on validator rea...
wml_type::ptr find_type(const std::string &type) const
#define ERR_VL
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:92
filesystem::scoped_istream preprocess_file(const std::string &fname, preproc_map *defines)
static void wrong_type_error(const std::string &file, int line, const std::string &tag, const std::string &key, const std::string &type, bool flag_exception)
static lg::log_domain log_validation("validation")
virtual void validate_key(const config &cfg, const std::string &name, const std::string &value, int start_line, const std::string &file) override
Checks if key is allowed and if its value is valid What exactly is validated depends on validator rea...
static map_location::DIRECTION n
virtual void validate(const config &cfg, const std::string &name, int start_line, const std::string &file) override
Validates config.
std::map< std::string, std::string > links_
std::vector< reference > referenced_tag_paths_
static void extra_tag_error(const std::string &file, int line, const std::string &name, int n, const std::string &parent, bool flag_exception)