/* * COPYRIGHT * * cschem - modular/flexible schematics editor - libcschem (core library) * Copyright (C) 2022 Tibor 'Igor2' Palinkas * * (Supported by NLnet NGI0 PET Fund in 2022) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version.* * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 31 Milk Street, # 960789 Boston, MA 02196 USA * * Contact: * Project page: http://repo.hu/projects/sch-rnd * contact lead developer: http://www.repo.hu/projects/sch-rnd/contact.html * mailing list: http://www.repo.hu/projects/sch-rnd/contact.html */ /* Data integrity check */ #include "config.h" #include #include "project.h" #include "cnc_any_obj.h" #include "cnc_conn.h" #include "intersect.h" #include "integrity.h" int csch_integrity_auto = 1; static void igy_brk() {} #define ASSERT1(expr, reason, arg1) \ do { \ if (!(expr)) { \ const char *sheetname = sheet->hidlib.loadname; \ rnd_message(RND_MSG_ERROR, "Integrity violation: " reason, arg1); \ rnd_message(RND_MSG_ERROR, " (" #expr ") at " CSCH_IGY_LOC ", sheet: %s; called from: %s\n", sheetname, called_from); \ igy_brk(); \ res |= 1; \ } \ } while(0) #define ASSERT(expr, reason) ASSERT1(expr, reason, NULL) RND_INLINE int igy_cnc_grp(void *parent, csch_cgrp_t *grp, const char *called_from); RND_INLINE int igy_cnc_conn(void *parent, csch_conn_t *c, const char *called_from) { int res = 0, had_two_grps = 0; csch_sheet_t *sheet = c->hdr.sheet; csch_cgrp_t *first = NULL; long n; if (c->conn_path.used != 0) csch_conn_text2ptr(sheet, c); ASSERT(c->conn.used != 0, "Can't resolve connections"); ASSERT(c->conn.used != 1, "Connection object with a single object ref"); /* require each object to be in a wirenet or symbol */ for(n = 0; n < c->conn.used; n++) { csch_chdr_t *obj = c->conn.array[n]; ASSERT(obj != NULL, "Connection object with invalid ref"); if (obj != NULL) { /* count if we have two differnet parent groups */ if (first == NULL) first = obj->parent; else if (first != obj->parent) had_two_grps = 1; ASSERT((obj->parent != NULL), "conn with no parent"); if (obj->parent != NULL) ASSERT((obj->parent->role == CSCH_ROLE_TERMINAL) || (obj->parent->role == CSCH_ROLE_WIRE_NET), "unusual parent group for connection referee"); } } ASSERT(had_two_grps, "Connection's all ref'd objects are of the same parent group"); return res; } RND_INLINE int igy_cnc_obj(csch_cgrp_t *parent, csch_chdr_t *obj, const char *called_from) { int res = 0; csch_sheet_t *sheet = obj->sheet; if (parent != NULL) ASSERT(parent == obj->parent, "broken object parent"); switch(obj->type) { case CSCH_CTYPE_LINE: case CSCH_CTYPE_TEXT: case CSCH_CTYPE_ARC: case CSCH_CTYPE_POLY: case CSCH_CTYPE_BITMAP: case CSCH_CTYPE_PEN: break; case CSCH_CTYPE_CONN: res |= igy_cnc_conn(NULL, (csch_conn_t *)obj, called_from); break; case CSCH_CTYPE_GRP: case CSCH_CTYPE_GRP_REF: res |= igy_cnc_grp(NULL, (csch_cgrp_t *)obj, called_from); break; case CSCH_CTYPE_invalid: case CSCH_CTYPE_max: ASSERT(0, "invalid object type"); } return res; } /* Recursive intersection search: set ->mark on any object that is in the same group and on the CSCH_DSPLY_WIRE layer that are reachable through intersections from obj */ RND_INLINE void igy_wirenet_find_segs_from(csch_chdr_t *obj) { csch_chdr_t *pair; csch_rtree_it_t it; /* collect all relevant intersections and mark them and schedule them for visit */ for(pair = csch_rtree_first(&it, &obj->sheet->dsply[CSCH_DSPLY_WIRE], &obj->bbox); pair != NULL; pair = csch_rtree_next(&it)) { csch_chdr_t *po = (csch_chdr_t *)pair; if (!po->mark && (po->parent == obj->parent)) { if (csch_obj_intersect_obj(obj->sheet, obj, po, NULL, 0)) { po->mark = 1; po->visit = 1; } } } /* execute scheduled visits (recursion) */ for(pair = csch_rtree_first(&it, &obj->sheet->dsply[CSCH_DSPLY_WIRE], &obj->bbox); pair != NULL; pair = csch_rtree_next(&it)) { csch_chdr_t *po = (csch_chdr_t *)pair; if (po->visit) { po->visit = 0; igy_wirenet_find_segs_from(po); } } } RND_INLINE int igy_wirenet(void *parent, csch_cgrp_t *wn, const char *called_from) { int res = 0; long wnlen = htip_length(&wn->id2obj); csch_sheet_t *sheet = wn->hdr.sheet; if (wnlen > 1) { htip_entry_t *e; csch_chdr_t *obj; for(e = htip_first(&wn->id2obj); e != NULL; e = htip_next(&wn->id2obj, e)) { csch_chdr_t *obj = e->value; obj->mark = obj->visit = 0; } /* find the first non-floater to start from */ obj = NULL; for(e = htip_first(&wn->id2obj); e != NULL; e = htip_next(&wn->id2obj, e)) { csch_chdr_t *o = e->value; if (!o->floater) { obj = o; break; } } if (obj != NULL) { obj->mark = 1; igy_wirenet_find_segs_from(obj); for(e = htip_first(&wn->id2obj); e != NULL; e = htip_next(&wn->id2obj, e)) { csch_chdr_t *obj = e->value; if (!obj->floater) ASSERT(obj->mark, "Disjoint wirenet"); obj->mark = obj->visit = 0; } } else { ASSERT1(0, "wirenet #%ld conists of floaters only", wn->hdr.oid); } } else if (wnlen == 1) { htip_entry_t *e = htip_first(&wn->id2obj); csch_chdr_t *o = e->value; ASSERT1(!o->floater, "wirenet #%ld conists of floaters only", wn->hdr.oid); } else if (wnlen < 1) ASSERT(parent == wn->hdr.parent, "empty wirenet (invisible ghost group)"); return res; } RND_INLINE int igy_cnc_grp(void *parent, csch_cgrp_t *grp, const char *called_from) { int res = 0, sym_mode_direct; htip_entry_t *e; csch_sheet_t *sheet = grp->hdr.sheet; if (parent != NULL) ASSERT(parent == grp->hdr.parent, "broken group parent"); if (grp->role == CSCH_ROLE_WIRE_NET) res |= igy_wirenet(parent, grp, called_from); if (grp->loclib_name != NULL) ASSERT(csch_cobj_is_indirect(&grp->hdr), "group with loclib_name set shall be under the indirect subtree"); /* check each child object's integrity */ for(e = htip_first(&grp->id2obj); e != NULL; e = htip_next(&grp->id2obj, e)) { csch_chdr_t *obj = e->value; ASSERT(obj->oid == e->key, "wrong ID in hash"); res |= igy_cnc_obj(grp, obj, called_from); } if (grp->hdr.type == CSCH_CTYPE_GRP_REF) { ASSERT((grp->data.ref.grp != NULL) || (grp->data.ref.ref_str != NULL), "grp_ref with no referee"); if (grp->data.ref.grp != NULL) ASSERT(grp->data.ref.grp->hdr.sheet == grp->hdr.sheet, "grp_ref referee is on another sheet"); } /* in symbol edit mode the sheet is a dummy sheet so does not have an uuid */ sym_mode_direct = sheet->is_symbol && (grp == &grp->hdr.sheet->direct); if (grp != &grp->hdr.sheet->indirect && !sym_mode_direct) ASSERT(!minuid_is_nil(grp->uuid), "grp without (instance) UUID"); return res; } RND_INLINE int igy_cnc_direct(csch_cgrp_t *grp, const char *called_from) { int res = 0; csch_sheet_t *sheet = grp->hdr.sheet; ASSERT(grp->spec_rot == 0, "sheet's direct group must not be rotated"); ASSERT(grp->x == 0, "sheet's direct group must not be translated (x)"); ASSERT(grp->y == 0, "sheet's direct group must not be translated (y)"); ASSERT(grp->mirx == 0, "sheet's direct group must not be mirrored (x)"); ASSERT(grp->miry == 0, "sheet's direct group must not be mirrored (y)"); return res; } RND_INLINE int igy_cnc_rtree(csch_sheet_t *sheet, csch_rtree_t *rtree, const char *called_from) { csch_rtree_it_t it; csch_chdr_t *o; int res = 0; for(o = csch_rtree_all_first(&it, rtree); o != NULL; o = csch_rtree_all_next(&it)) { ASSERT(!csch_obj_is_deleted(o), "Ghost object: presents in rtree but has been deleted"); } return res; } RND_INLINE int igy_cnc_rtrees(csch_sheet_t *sheet, const char *called_from) { int n, res = 0; for(n = 0; n < CSCH_DSPLY_max; n++) { res |= igy_cnc_rtree(sheet, &sheet->dsply[n], called_from); res |= igy_cnc_rtree(sheet, &sheet->dsply_fill[n], called_from); } return res; } RND_INLINE int igy_sheet(csch_project_t *proj, csch_sheet_t *sheet, const char *called_from) { int res = 0; if (sheet->non_graphical) return 0; /* don't check them for now */ if (proj != NULL) ASSERT(proj == (csch_project_t *)sheet->hidlib.project, "broken sheet parent"); ASSERT(sheet->direct.hdr.parent == NULL, "broken direct grp parent"); ASSERT(sheet->indirect.hdr.parent == NULL, "broken indirect grp parent"); res |= igy_cnc_direct(&sheet->direct, called_from); res |= igy_cnc_grp(NULL, &sheet->direct, called_from); res |= igy_cnc_grp(NULL, &sheet->indirect, called_from); res |= igy_cnc_rtrees(sheet, called_from); return res; } int csch_integrity_sheet(csch_sheet_t *sheet, const char *called_from) { return igy_sheet(NULL, sheet, called_from); } int csch_integrity_project(csch_project_t *proj, const char *called_from) { long n; int res = 0; for(n = 0; n < proj->hdr.designs.used; n++) res |= igy_sheet(proj, proj->hdr.designs.array[n], called_from); return res; } static const char integrity_cookie[] = "libcschem/integrity.c"; static const char csch_acts_Integrity[] = "Integrity()"; static const char csch_acth_Integrity[] = "Run data integrity checks on current sheet."; fgw_error_t csch_act_Integrity(fgw_arg_t *res, int argc, fgw_arg_t *argv) { csch_sheet_t *sheet = CSCH_ACT_SHEET; RND_ACT_IRES(csch_integrity_sheet(sheet, "Integrity() action")); return 0; } static rnd_action_t csch_integrity_act_list[] = { {"Integrity", csch_act_Integrity, csch_acth_Integrity, csch_acts_Integrity} }; void csch_integrity_act_init(void) { RND_REGISTER_ACTIONS(csch_integrity_act_list, integrity_cookie); } void csch_integrity_act_uninit(void) { rnd_remove_actions_by_cookie(integrity_cookie); }