// file : odb/validator.cxx // copyright : Copyright (c) 2009-2015 Code Synthesis Tools CC // license : GNU GPL v3; see accompanying LICENSE file #include #include #include #include #include #include #include #include #include using namespace std; namespace { // Resolve null overrides. // static void override_null (semantics::node& n, string const& prefix = "") { string p (prefix.empty () ? prefix : prefix + '-'); if (n.count (p + "null") && n.count (p + "not-null")) { if (n.get (p + "null-location") < n.get (p + "not-null-location")) { n.remove (p + "null"); n.remove (p + "null-location"); } else { n.remove (p + "not-null"); n.remove (p + "not-null-location"); } } } // // Pass 1. // struct data_member1: traversal::data_member, context { data_member1 (bool& valid) : valid_ (valid) { } virtual void traverse (type& m) { semantics::class_& c (dynamic_cast (m.scope ())); bool obj (object (c)); // If the class is marked transient, then mark each non-virtual // data member as transient. // { bool t (transient (m)); if (!t && c.count ("transient") && !m.count ("virtual")) { m.set ("transient", true); t = true; } if (t) return; } semantics::names* hint; semantics::type& t (utype (m, hint)); if (t.fq_anonymous (hint)) { os << m.file () << ":" << m.line () << ":" << m.column () << ":" << " error: unnamed type in data member declaration" << endl; os << m.file () << ":" << m.line () << ":" << m.column () << ":" << " info: use 'typedef' to name this type" << endl; valid_ = false; } // Make sure id or inverse member is not marked readonly since we // depend on these three sets not having overlaps. // if (m.count ("readonly")) // context::readonly() also checks the class. { if (id (m)) { os << m.file () << ":" << m.line () << ":" << m.column () << ":" << " error: object id should not be declared readonly" << endl; valid_ = false; } if (inverse (m)) { os << m.file () << ":" << m.line () << ":" << m.column () << ":" << " error: inverse object pointer should not be declared " << "readonly" << endl; valid_ = false; } } // Make sure a member of a section is an immediate member of an object. // The same for the section member itself. // bool section (t.fq_name () == "::odb::section"); if (!obj) { if (m.count ("section-member")) { os << m.file () << ":" << m.line () << ":" << m.column () << ": " << "error: data member belonging to a section can only be a " << "direct member of a persistent class" << endl; valid_ = false; } if (section) { os << m.file () << ":" << m.line () << ":" << m.column () << ": " << "error: section data member can only be a direct member of a " << "persistent class" << endl; valid_ = false; } } // Make sure the load and update pragmas are only specified on // section members. // if (!section) { if (m.count ("section-load")) { location_t loc (m.get ("section-load-location")); error (loc) << "'#pragma db load' can only be specified for " "a section data member" << endl; valid_ = false; } if (m.count ("section-update")) { location_t loc (m.get ("section-update-location")); error (loc) << "'#pragma db update' can only be specified for " "a section data member" << endl; valid_ = false; } } // Check that the addition version makes sense. // unsigned long long av (m.get ("added", 0)); if (av != 0) { location_t l (m.get ("added-location")); if (id (m)) { error (l) << "object id cannod be soft-added" << endl; valid_ = false; } if (version (m)) { error (l) << "optimistic concurrency version cannod be " "soft-added" << endl; valid_ = false; } if (!versioned ()) { error (l) << "added data member in a non-versioned object " << "model" << endl; valid_ = false; } else { model_version const& mv (version ()); if (av > mv.current) { error (l) << "addition version is greater than the current " << "model version" << endl; valid_ = false; } else if (av <= mv.base) { error (l) << "addition version is less than or equal to the " << "base model version" << endl; info (l) << "delete this pragma since migration to version " << av << " is no longer possible" << endl; valid_ = false; } } } // Check that the deletion version makes sense. // unsigned long long dv (m.get ("deleted", 0)); if (dv != 0) { location_t l (m.get ("deleted-location")); if (id (m)) { error (l) << "object id cannod be soft-deleted" << endl; valid_ = false; } if (version (m)) { error (l) << "optimistic concurrency version cannod be " "soft-deleted" << endl; valid_ = false; } if (!versioned ()) { error (l) << "deleted data member in a non-versioned object " << "model" << endl; valid_ = false; } else { model_version const& mv (version ()); if (dv > mv.current) { error (l) << "deletion version is greater than the current " << "model version" << endl; valid_ = false; } else if (dv <= mv.base) { error (l) << "deletion version is less than or equal to the " << "base model version" << endl; info (c.location ()) << "delete this data member since " << "migration to version " << dv << " is no longer possible" << endl; valid_ = false; } } } // Make sure that addition and deletion versions are properly ordered. // We can have both the [av, dv] (added then deleted) and [dv, av] // (deleted then re-added) intervals. // if (av != 0 && dv != 0 && av == dv) { location_t al (m.get ("added-location")); location_t dl (m.get ("deleted-location")); error (al) << "addition and deletion versions are the same" << endl; info (dl) << "deletion version is specified here" << endl; valid_ = false; } if (section) return; // Section data member is transient. // Resolve null overrides. // override_null (m); override_null (m, "value"); } bool& valid_; }; // Find special members (id, version). // struct special_members: traversal::class_, context { special_members (class_kind_type kind, bool& valid, semantics::data_member*& id, semantics::data_member*& optimistic) : kind_ (kind), member_ (valid, id, optimistic) { if (kind != class_view) *this >> inherits_ >> *this; *this >> names_ >> member_; } virtual void traverse (semantics::class_& c) { // Skip transient bases. // switch (kind_) { case class_object: { if (!object (c)) return; break; } case class_view: { break; } case class_composite: { if (!composite (c)) return; break; } case class_other: { assert (false); break; } } // Views don't have bases. // if (kind_ != class_view) inherits (c); names (c); } private: struct member: traversal::data_member, context { member (bool& valid, semantics::data_member*& id, semantics::data_member*& optimistic) : valid_ (valid), id_ (id), optimistic_ (optimistic) { } virtual void traverse (semantics::data_member& m) { if (id (m)) { if (id_ == 0) id_ = &m; else { os << m.file () << ":" << m.line () << ":" << m.column () << ":" << " error: multiple object id members" << endl; os << id_->file () << ":" << id_->line () << ":" << id_->column () << ": info: previous id member is declared here" << endl; valid_ = false; } } if (version (m)) { if (optimistic_ == 0) optimistic_ = &m; else { os << m.file () << ":" << m.line () << ":" << m.column () << ":" << " error: multiple version members" << endl; semantics::data_member& o (*optimistic_); os << o.file () << ":" << o.line () << ":" << o.column () << ": info: previous version member is declared here" << endl; valid_ = false; } } } bool& valid_; semantics::data_member*& id_; semantics::data_member*& optimistic_; }; class_kind_type kind_; member member_; traversal::names names_; traversal::inherits inherits_; }; // // struct value_type: traversal::type, context { virtual void traverse (semantics::type& t) { // Resolve null overrides. // override_null (t); override_null (t, "value"); } }; struct typedefs1: typedefs { typedefs1 (traversal::declares& d) : typedefs (true), declares_ (d) { } void traverse (semantics::typedefs& t) { if (check (t)) traversal::typedefs::traverse (t); else declares_.traverse (t); } private: traversal::declares& declares_; }; // // struct class1: traversal::class_, context { class1 (bool& valid) : valid_ (valid), typedefs_ (declares_), member_ (valid) { *this >> defines_ >> *this; *this >> typedefs_ >> *this; *this >> declares_ >> vt_; names_member_ >> member_; } virtual void traverse (type& c) { switch (class_kind (c)) { case class_object: names (c); traverse_object (c); break; case class_view: names (c); traverse_view (c); break; case class_composite: names (c); traverse_composite (c); // Fall through. case class_other: vt_.dispatch (c); break; } } virtual void traverse_object (type& c) { // Check that the deletion version makes sense. // if (unsigned long long v = c.get ("deleted", 0)) { location_t l (c.get ("deleted-location")); if (!versioned ()) { error (l) << "deleted class in a non-versioned object model" << endl; valid_ = false; } else { model_version const& mv (version ()); if (v > mv.current) { error (l) << "deletion version is greater than the current " << "model version" << endl; valid_ = false; } else if (v <= mv.base) { error (l) << "deletion version is less than or equal to the " << "base model version" << endl; info (c.location ()) << "delete this class since migration to " << "version " << v << " is no longer possible" << endl; valid_ = false; } } } // Check that the callback function exist. // if (c.count ("callback")) { string name (c.get ("callback")); tree decl ( lookup_qualified_name ( c.tree_node (), get_identifier (name.c_str ()), false, false)); if (decl == error_mark_node || TREE_CODE (decl) != BASELINK) { os << c.file () << ":" << c.line () << ":" << c.column () << ": " << "error: unable to resolve member function '" << name << "' " << "specified with '#pragma db callback' for class '" << class_name (c) << "'" << endl; valid_ = false; } // Figure out if we have a const version of the callback. OVL_* // macros work for both FUNCTION_DECL and OVERLOAD. // for (tree o (BASELINK_FUNCTIONS (decl)); o != 0; o = OVL_NEXT (o)) { tree f (OVL_CURRENT (o)); if (DECL_CONST_MEMFUNC_P (f)) { c.set ("callback-const", true); break; } } //@@ Would be nice to check the signature of the function(s) // instead of postponing it until the C++ compilation. Though // we may still get C++ compilation errors because of const // mismatch. // } // Check bases. // type* poly_root (0); for (type::inherits_iterator i (c.inherits_begin ()); i != c.inherits_end (); ++i) { type& b (i->base ()); if (object (b)) { if (type* r = polymorphic (b)) { if (poly_root == 0) { poly_root = r; c.set ("polymorphic-base", &static_cast (b)); } // If poly_root and r are the same, then we have virtual // inheritance. Though we don't support it at the moment. // else //if (poly_root != r) { os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " error: persistent class '" << class_name (c) << "' " << "derives from multiple polymorphic bases" << endl; type& a (*poly_root); os << a.file () << ":" << a.line () << ":" << a.column () << ":" << " info: first polymorphic base defined here" << endl; type& b (*r); os << b.file () << ":" << b.line () << ":" << b.column () << ":" << " info: second polymorphic base defined here" << endl; valid_ = false; } } } else if (view (b) || composite (b)) { // @@ Should we use hint here? // string name (class_fq_name (b)); os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " error: base class '" << name << "' is a view or value type" << endl; os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " info: object types cannot derive from view or value " << "types" << endl; os << b.file () << ":" << b.line () << ":" << b.column () << ":" << " info: class '" << name << "' is defined here" << endl; valid_ = false; } } // Check members. // names (c, names_member_); // Check special members. // semantics::data_member* id (0); semantics::data_member* optimistic (0); { special_members t (class_object, valid_, id, optimistic); t.traverse (c); } if (id == 0) { // An object without an id should either be reuse-abstract // or explicitly marked as such. We check that it is not // polymorphic below. // if (!(c.count ("id") || abstract (c))) { os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " error: no data member designated as an object id" << endl; os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " info: use '#pragma db id' to specify an object id member" << endl; os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " info: or explicitly declare that this persistent class " << "has no object id with '#pragma db object no_id'" << endl; valid_ = false; } } else { c.set ("id-member", id); // Complain if an id member has a default value (default value // for the id's type is ok -- we will ignore it). // if (id->count ("default")) { os << id->file () << ":" << id->line () << ":" << id->column () << ": error: object id member cannot have default value" << endl; valid_ = false; } // Complain if an id member is in a section. // if (id->count ("section-member")) { os << id->file () << ":" << id->line () << ":" << id->column () << ": error: object id member cannot be in a section" << endl; valid_ = false; } // Automatically mark the id member as not null. If we already have // an explicit null pragma for this member, issue an error. // if (id->count ("null")) { os << id->file () << ":" << id->line () << ":" << id->column () << ": error: object id member cannot be null" << endl; valid_ = false; } else id->set ("not-null", true); } if (optimistic != 0) { semantics::data_member& m (*optimistic); // Make sure we have the class declared optimistic. // if (&m.scope () == &c && !c.count ("optimistic")) { os << m.file () << ":" << m.line () << ":" << m.column () << ":" << " error: version data member in a class not declared " << "optimistic" << endl; os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " info: use '#pragma db optimistic' to declare this " << "class optimistic" << endl; valid_ = false; } // Make sure we have object id. // if (id == 0) { os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " error: optimistic class without an object id" << endl; valid_ = false; } // Make sure id and version members are in the same class. The // current architecture relies on that. // if (id != 0 && &id->scope () != &m.scope ()) { os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " error: object id and version members are in different " << "classes" << endl; os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " info: object id and version members must be in the same " << "class" << endl; os << id->file () << ":" << id->line () << ":" << id->column () << ": info: object id member is declared here" << endl; os << m.file () << ":" << m.line () << ":" << m.column () << ":" << " error: version member is declared here" << endl; valid_ = false; } // Make sure this class is not readonly. // if (readonly (c)) { os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " error: optimistic class cannot be readonly" << endl; valid_ = false; } // Complain if the version member is in a section. // if (m.count ("section-member")) { os << m.file () << ":" << m.line () << ":" << m.column () << ": error: version member cannot be in a section" << endl; valid_ = false; } // This takes care of also marking derived classes as optimistic. // c.set ("optimistic-member", optimistic); } else { // Make sure there is a version member if the class declared // optimistic. // if (c.count ("optimistic")) { os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " error: optimistic class without a version member" << endl; os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " info: use '#pragma db version' to declare on of the " << "data members as a version" << endl; valid_ = false; } } // Polymorphic inheritance. // if (c.count ("polymorphic") && poly_root == 0) { // Root of the hierarchy. // if (id == 0) { os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " error: polymorphic class without an object id" << endl; valid_ = false; } if (!TYPE_POLYMORPHIC_P (c.tree_node ())) { os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " error: non-polymorphic class (class without virtual " << "functions) cannot be declared polymorphic" << endl; valid_ = false; } poly_root = &c; } if (poly_root != 0) c.set ("polymorphic-root", poly_root); // Sectionable objects. // if (c.count ("sectionable")) { if (optimistic == 0) { location_t l (c.get ("sectionable-location")); error (l) << "only optimistic class can be sectionable" << endl; valid_ = false; } else if (&optimistic->scope () != &c && poly_root != &c) { location l (c.get ("sectionable-location")); error (l) << "only optimistic class that declares the version " << "data member or that is a root of a polymorphic hierarchy can " << "be sectionable" << endl; info (optimistic->location ()) << "version member is declared " << "here" << endl; valid_ = false; } } // Update features set based on this object. // if (options.at_once () || class_file (c) == unit.file ()) { if (poly_root != 0) features.polymorphic_object = true; else if (id == 0 && !abstract (c)) features.no_id_object = true; else features.simple_object = true; } } virtual void traverse_view (type& c) { // Views require query support. // if (!options.generate_query ()) { os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " error: query support is required when using views" << endl; os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " info: use the --generate-query option to enable query " << "support" << endl; valid_ = false; } // Check that the callback function exist. // if (c.count ("callback")) { string name (c.get ("callback")); tree decl ( lookup_qualified_name ( c.tree_node (), get_identifier (name.c_str ()), false, false)); if (decl == error_mark_node || TREE_CODE (decl) != BASELINK) { os << c.file () << ":" << c.line () << ":" << c.column () << ": " << "error: unable to resolve member function '" << name << "' " << "specified with '#pragma db callback' for class '" << class_name (c) << "'" << endl; valid_ = false; } // No const version for views. //@@ Would be nice to check the signature of the function(s) // instead of postponing it until the C++ compilation. Though // we may still get C++ compilation errors because of const // mismatch. // } // Check bases. // for (type::inherits_iterator i (c.inherits_begin ()); i != c.inherits_end (); ++i) { type& b (i->base ()); if (object (b) || view (b) || composite (b)) { // @@ Should we use hint here? // string name (class_fq_name (b)); os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " error: base class '" << name << "' is an object, " << "view, or value type" << endl; os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " info: view types cannot derive from view, object or " << "value types" << endl; os << b.file () << ":" << b.line () << ":" << b.column () << ":" << " info: class '" << name << "' is defined here" << endl; valid_ = false; } } // Check members. // names (c, names_member_); // Check id. // semantics::data_member* id (0); semantics::data_member* optimistic (0); { special_members t (class_view, valid_, id, optimistic); t.traverse (c); } if (id != 0) { os << id->file () << ":" << id->line () << ":" << id->column () << ": error: view type data member cannot be designated as an " << "object id" << endl; valid_ = false; } if (optimistic != 0) { semantics::data_member& o (*optimistic); os << o.file () << ":" << o.line () << ":" << o.column () << ": error: view type data member cannot be designated as a " << "version" << endl; valid_ = false; } // Update features set based on this view. // if (options.at_once () || class_file (c) == unit.file ()) { features.view = true; } } virtual void traverse_composite (type& c) { for (type::inherits_iterator i (c.inherits_begin ()); i != c.inherits_end (); ++i) { type& b (i->base ()); if (object (b) || view (b)) { // @@ Should we use hint here? // string name (class_fq_name (b)); os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " error: base class '" << name << "' is a view or object " << "type" << endl; os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " info: composite value types cannot derive from object " << "or view types" << endl; os << b.file () << ":" << b.line () << ":" << b.column () << ":" << " info: class '" << name << "' is defined here" << endl; valid_ = false; } } // Check members. // names (c, names_member_); // Check id. // semantics::data_member* id (0); semantics::data_member* optimistic (0); { special_members t (class_composite, valid_, id, optimistic); t.traverse (c); } if (id != 0) { os << id->file () << ":" << id->line () << ":" << id->column () << ": error: value type data member cannot be designated as an " << "object id" << endl; valid_ = false; } if (optimistic != 0) { semantics::data_member& o (*optimistic); os << o.file () << ":" << o.line () << ":" << o.column () << ": error: value type data member cannot be designated as a " << "version" << endl; valid_ = false; } } bool& valid_; traversal::defines defines_; traversal::declares declares_; typedefs1 typedefs_; value_type vt_; data_member1 member_; traversal::names names_member_; }; // // Pass 2. // struct data_member2: traversal::data_member, context { data_member2 (bool& valid): valid_ (valid) {} virtual void traverse (type& m) { // Validate pointed-to objects. // semantics::class_* c; if ((c = object_pointer (utype (m))) || (c = points_to (m))) { bool poly (polymorphic (*c)); bool abst (abstract (*c)); // Make sure the pointed-to class is complete. // if (!c->complete ()) { os << m.file () << ":" << m.line () << ":" << m.column () << ": " << "error: pointed-to class '" << class_fq_name (*c) << "' " << "is incomplete" << endl; os << c->file () << ":" << c->line () << ":" << c->column () << ": " << "info: class '" << class_name (*c) << "' is declared here" << endl; os << c->file () << ":" << c->line () << ":" << c->column () << ": " << "info: consider including its definition with the " << "--odb-epilogue option" << endl; valid_ = false; return; } // Make sure the pointed-to class is not reuse-abstract. // if (abst && !poly) { os << m.file () << ":" << m.line () << ":" << m.column () << ": " << "error: pointed-to class '" << class_fq_name (*c) << "' " << "is abstract" << endl; os << c->file () << ":" << c->line () << ":" << c->column () << ": " << "info: class '" << class_name (*c) << "' is defined here" << endl; throw operation_failed (); } // Make sure the pointed-to class has object id unless it is in a // view where we can load no-id objects. // if (id_member (*c) == 0 && !view_member (m)) { os << m.file () << ":" << m.line () << ":" << m.column () << ": " << "error: pointed-to class '" << class_fq_name (*c) << "' " << "has no object id" << endl; os << c->file () << ":" << c->line () << ":" << c->column () << ": " << "info: class '" << class_name (*c) << "' is defined here" << endl; valid_ = false; return; } // Make sure the pointed-to class has a default ctor. Since we will // use database::load() in the generated code, lack of a default ctor // will lead to uncompilable generated code. Poly-abstract is Ok. // if (!c->default_ctor () && !(abst && poly)) { os << m.file () << ":" << m.line () << ":" << m.column () << ": " << "error: pointed-to class '" << class_fq_name (*c) << "' " << "has no default constructor" << endl; os << c->file () << ":" << c->line () << ":" << c->column () << ": " << "info: class '" << class_name (*c) << "' is defined here" << endl; valid_ = false; return; } } } bool& valid_; }; // Make sure soft-delete versions make sense for dependent entities. // We don't seem to need anything for soft-add since if an entity is // not added (e.g., an object), then we cannot reference it in the // C++ sense (e.g., a pointer). // struct version_dependencies: object_members_base { version_dependencies (bool& valid): valid_ (valid) {} virtual void traverse_object (semantics::class_& c) { // For reuse inheritance we allow the base to be soft-deleted. In // a sense, it becomes like an abstract class with the added // functionality of being able to load old data. // // For polymorphism inheritance things are a lot more complicated // and we don't allow a base to be soft-deleted since there is a // link (foreign key) from the derived table. // semantics::class_* poly (polymorphic (c)); if (poly != 0 && poly != &c) { semantics::class_& base (polymorphic_base (c)); check_strict ( c, base, "polymorphic derived object", "polymorphic base"); } // Don't go into bases. // names (c); } virtual void traverse_simple (semantics::data_member& m) { semantics::class_& c (dynamic_cast (m.scope ())); switch (class_kind (c)) { case class_object: { // Member shouldn't be deleted after the object that contains it. // check_lax (m, c, "data member", "object"); break; } // We leave it up to the user to make sure the view is consistent // with the database schema it is being run on. // default: break; } } virtual void traverse_composite (semantics::data_member* m, semantics::class_& c) { if (m != 0) traverse_simple (*m); // Do simple value tests. else names (c); // Don't go into bases. } virtual void traverse_pointer (semantics::data_member& m, semantics::class_& c) { traverse_simple (m); // Do simple value tests. // Pointer must be deleted before the pointed-to object. // check_strict (m, c, "object pointer", "pointed-to object"); // Inverse pointer must be deleted before the direct side. // if (data_member_path* imp = inverse (m)) check_strict (m, *imp, "inverse object pointer", "direct pointer"); } virtual void traverse_container (semantics::data_member& m, semantics::type& t) { traverse_simple (m); // Do simple value tests. if (semantics::class_* c = object_pointer (container_vt (t))) { // Pointer must be deleted before the pointed-to object. // check_strict (m, *c, "object pointer", "pointed-to object"); // Inverse pointer must be deleted before the direct side. // if (data_member_path* imp = inverse (m, "value")) check_strict (m, *imp, "inverse object pointer", "direct pointer"); } } private: // Check for harmless things like a data member deleted after the // object. // template void check_lax (D& dep, P& pre, char const* dname, char const* pname) { unsigned long long dv (deleted (dep)); unsigned long long pv (deleted (pre)); if (pv == 0 || // Prerequisite never deleted. dv == 0 || // Dependent never deleted. dv <= pv) // Dependent deleted before prerequisite. return; location_t dl (dep.template get ("deleted-location")); location_t pl (pre.template get ("deleted-location")); error (dl) << dname << " is deleted after " << dname << endl; info (pl) << pname << " deletion version is specified here" << endl; valid_ = false; } // Check for harmful things that will result in an invalid database // schema, like a pointer pointing to a deleted object. // template void check_strict (D& dep, P& pre, char const* dname, char const* pname) { location_t dl, pl; unsigned long long dv (deleted (dep, &dl)); unsigned long long pv (deleted (pre, &pl)); if (pv == 0) // Prerequisite never deleted. return; if (dv == 0) // Dependent never deleted. { location dl (dep.location ()); error (dl) << dname << " is not deleted" << endl; info (pl) << pname << " is deleted here" << endl; valid_ = false; } else if (pv > dv) // Prerequisite deleted before dependent. { error (dl) << dname << " is deleted after " << pname << endl; info (pl) << pname << " deletion version is specified here" << endl; valid_ = false; } } private: bool& valid_; }; struct class2: traversal::class_, context { class2 (bool& valid) : valid_ (valid), has_lt_operator_ (0), typedefs_ (true), member_ (valid) { // Find the has_lt_operator function template. // tree odb ( lookup_qualified_name ( global_namespace, get_identifier ("odb"), false, false)); if (odb != error_mark_node) { tree compiler ( lookup_qualified_name ( odb, get_identifier ("compiler"), false, false)); if (compiler != error_mark_node) { has_lt_operator_ = lookup_qualified_name ( compiler, get_identifier ("has_lt_operator"), false, false); if (has_lt_operator_ != error_mark_node) has_lt_operator_ = OVL_CURRENT (has_lt_operator_); else { os << unit.file () << ": error: unable to resolve has_lt_operator " << "function template inside odb::compiler" << endl; has_lt_operator_ = 0; } } else os << unit.file () << ": error: unable to resolve compiler " << "namespace inside odb" << endl; } else os << unit.file () << ": error: unable to resolve odb namespace" << endl; if (has_lt_operator_ == 0) valid_ = false; *this >> defines_ >> *this; *this >> typedefs_ >> *this; names_member_ >> member_; } virtual void traverse (type& c) { class_kind_type ck (class_kind (c)); if (ck == class_other) return; names (c); switch (ck) { case class_object: traverse_object (c); break; case class_view: traverse_view (c); break; case class_composite: traverse_composite (c); break; default: return; } // Check members. // names (c, names_member_); // Check version dependencies. // { version_dependencies vd (valid_); vd.traverse (c); } } virtual void traverse_object (type& c) { bool abst (abstract (c)); bool poly (polymorphic (c)); // Make sure we have no empty or pointless sections unless we // are reuse-abstract or polymorphic. // if (!poly && !abst) { user_sections& uss (c.get ("user-sections")); for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) { user_section& s (*i); // Skip the special version update section (we always treat it // as abstract in reuse inheritance). // if (s.special == user_section::special_version) continue; semantics::data_member& m (*s.member); location const& l (m.location ()); if (s.total == 0 && !s.containers) { error (l) << "empty section" << endl; if (&m.scope () != &c) info (c.location ()) << "as seen in this non-abstract " << "persistent class" << endl; valid_ = false; continue; } // Eager-loaded section with readonly members. // if (s.load == user_section::load_eager && s.update_empty ()) { error (l) << "eager-loaded section with readonly members is " << "pointless" << endl; if (&m.scope () != &c) info (c.location ()) << "as seen in this non-abstract " << "persistent class" << endl; valid_ = false; } } } if (semantics::data_member* id = id_member (c)) { semantics::type& t (utype (*id)); // If this is a session object, make sure that the id type can // be compared. // if (session (c) && has_lt_operator_ != 0) { tree args (make_tree_vec (1)); TREE_VEC_ELT (args, 0) = t.tree_node (); tree inst ( instantiate_template ( has_lt_operator_, args, tf_none)); bool v (inst != error_mark_node); if (v && DECL_TEMPLATE_INSTANTIATION (inst) && !DECL_TEMPLATE_INSTANTIATED (inst)) { // Instantiate this function template to see if the value type // provides operator<. Unfortunately, GCC instantiate_decl() // does not provide any control over the diagnostics it issues // in case of an error. To work around this, we are going to // temporarily redirect diagnostics to /dev/null, which is // where asm_out_file points to (see plugin.cxx). // int ec (errorcount); FILE* s (global_dc->printer->buffer->stream); global_dc->printer->buffer->stream = asm_out_file; instantiate_decl (inst, false, false); global_dc->printer->buffer->stream = s; v = (ec == errorcount); } if (!v) { os << t.file () << ":" << t.line () << ":" << t.column () << ": error: value type that is used as object id in " << "persistent class with session support does not define " << "the less than (<) comparison" << endl; os << t.file () << ":" << t.line () << ":" << t.column () << ": info: provide operator< for this value type" << endl; os << id->file () << ":" << id->line () << ":" << id->column () << ": info: id member is defined here" << endl; os << c.file () << ":" << c.line () << ":" << c.column () << ": info: persistent class is defined here" << endl; valid_ = false; } } } else { // Make sure an object without id has no sections. // user_sections& uss (c.get ("user-sections")); if (!uss.empty ()) { semantics::data_member& m (*uss.front ().member); os << m.file () << ":" << m.line () << ":" << m.column () << ": error: object without id cannot have sections" << endl; valid_ = false; } } // Allow all the members to be deleted as long as there is no // schema for this class. // column_count_type const& cc (column_count (c)); size_t cont (has_a (c, test_container)); size_t dcont (cont - has_a (c, test_container | exclude_deleted)); if ((cc.total == 0 && cont == 0) || (cc.total == cc.deleted && cont == dcont && !(abst || deleted (c)))) { os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " error: no persistent data members in the class" << endl; valid_ = false; } } virtual void traverse_view (type& c) { // We don't check for the column count here since we may want to // allow certain kinds of empty views. Instead, this is handled // in relational::validation. // See if any of the associated objects are polymorphic. If so, // we need to include polymorphism-specific headers. // if (c.count ("objects")) { view_objects& objs (c.get ("objects")); for (view_objects::iterator i (objs.begin ()); i != objs.end (); ++i) { if (i->kind == view_object::object && polymorphic (*i->obj)) features.polymorphic_object = true; } } } virtual void traverse_composite (type& c) { // Allow all the members to be deleted. // column_count_type const& cc (column_count (c)); size_t cont (has_a (c, test_container)); if (cc.total == 0 && cont == 0) { os << c.file () << ":" << c.line () << ":" << c.column () << ":" << " error: no persistent data members in the class" << endl; valid_ = false; } } bool& valid_; tree has_lt_operator_; traversal::defines defines_; typedefs typedefs_; data_member2 member_; traversal::names names_member_; }; } void validate (options const& ops, features& f, semantics::unit& u, semantics::path const& p, unsigned short pass) { bool valid (true); database db (ops.database ()[0]); // Validate options. // if (ops.generate_schema_only () && ops.schema_format ()[db].count (schema_format::embedded)) { cerr << "error: --generate-schema-only is only valid when generating " << "schema as a standalone SQL or separate C++ file" << endl; valid = false; } // Multi-database support options. // if (ops.multi_database () == multi_database::dynamic && ops.default_database_specified () && ops.default_database () != database::common) { cerr << "error: when dynamic multi-database support is used, the " << "default database can only be 'common'" << endl; valid = false; } if (db == database::common && ops.multi_database () == multi_database::disabled) { cerr << "error: 'common' database is only valid with multi-database " << "support enabled" << endl; valid = false; } // Changelog options. // if (ops.changelog_in ().count (db) != ops.changelog_out ().count (db)) { cerr << "error: both --changelog-in and --changelog-out must be " << "specified" << endl; valid = false; } if (!valid) throw validator_failed (); auto_ptr ctx (create_context (cerr, u, ops, f, 0)); if (pass == 1) { traversal::unit unit; traversal::defines unit_defines; traversal::declares unit_declares; typedefs1 unit_typedefs (unit_declares); traversal::namespace_ ns; value_type vt; class1 c (valid); unit >> unit_defines >> ns; unit_defines >> c; unit >> unit_declares >> vt; unit >> unit_typedefs >> c; traversal::defines ns_defines; traversal::declares ns_declares; typedefs1 ns_typedefs (ns_declares); ns >> ns_defines >> ns; ns_defines >> c; ns >> ns_declares >> vt; ns >> ns_typedefs >> c; unit.dispatch (u); } else { traversal::unit unit; traversal::defines unit_defines; typedefs unit_typedefs (true); traversal::namespace_ ns; class2 c (valid); unit >> unit_defines >> ns; unit_defines >> c; unit >> unit_typedefs >> c; traversal::defines ns_defines; typedefs ns_typedefs (true); ns >> ns_defines >> ns; ns_defines >> c; ns >> ns_typedefs >> c; unit.dispatch (u); } if (!valid) throw validator_failed (); switch (db) { case database::common: { break; } case database::mssql: case database::mysql: case database::oracle: case database::pgsql: case database::sqlite: { try { relational::validate (ops, f, u, p, pass); } catch (operation_failed const&) { throw validator_failed (); } break; } } }