diff --git a/c/tests/test_tables.c b/c/tests/test_tables.c index 9ac80f86fe..c2d310d2b5 100644 --- a/c/tests/test_tables.c +++ b/c/tests/test_tables.c @@ -4087,6 +4087,48 @@ test_sort_tables_mutation_times(void) tsk_treeseq_free(&ts); } +static void +test_sort_tables_canonical_errors(void) +{ + int ret; + tsk_table_collection_t tables; + ret = tsk_table_collection_init(&tables, 0); + CU_ASSERT_EQUAL_FATAL(ret, 0); + tables.sequence_length = 1; + + ret = tsk_node_table_add_row(&tables.nodes, 0, 0.0, TSK_NULL, TSK_NULL, NULL, 0); + CU_ASSERT_FATAL(ret >= 0); + ret = tsk_site_table_add_row(&tables.sites, 0.0, "x", 1, NULL, 0); + CU_ASSERT_FATAL(ret >= 0); + ret = tsk_mutation_table_add_row(&tables.mutations, 0, 0, 2, 0.0, "a", 1, NULL, 0); + CU_ASSERT_FATAL(ret >= 0); + ret = tsk_mutation_table_add_row(&tables.mutations, 0, 0, 3, 0.0, "b", 1, NULL, 0); + CU_ASSERT_FATAL(ret >= 0); + ret = tsk_mutation_table_add_row(&tables.mutations, 0, 0, 1, 0.0, "c", 1, NULL, 0); + CU_ASSERT_FATAL(ret >= 0); + ret = tsk_mutation_table_add_row(&tables.mutations, 0, 0, 2, 0.0, "d", 1, NULL, 0); + CU_ASSERT_FATAL(ret >= 0); + + ret = tsk_table_collection_canonicalise(&tables, 0); + CU_ASSERT_EQUAL_FATAL(ret, TSK_ERR_MUTATION_PARENT_INCONSISTENT); + + ret = tsk_mutation_table_clear(&tables.mutations); + CU_ASSERT_FATAL(ret >= 0); + ret = tsk_mutation_table_add_row(&tables.mutations, 0, 0, 2, 0.0, "a", 1, NULL, 0); + CU_ASSERT_FATAL(ret >= 0); + ret = tsk_mutation_table_add_row(&tables.mutations, 0, 0, 3, 0.0, "b", 1, NULL, 0); + CU_ASSERT_FATAL(ret >= 0); + ret = tsk_mutation_table_add_row(&tables.mutations, 0, 0, 1, 0.0, "c", 1, NULL, 0); + CU_ASSERT_FATAL(ret >= 0); + ret = tsk_mutation_table_add_row(&tables.mutations, 0, 0, -1, 0.0, "d", 1, NULL, 0); + CU_ASSERT_FATAL(ret >= 0); + + ret = tsk_table_collection_canonicalise(&tables, 0); + CU_ASSERT_EQUAL_FATAL(ret, 0); + + tsk_table_collection_free(&tables); +} + static void test_sort_tables_canonical(void) { @@ -6028,6 +6070,7 @@ main(int argc, char **argv) { "test_edge_update_invalidates_index", test_edge_update_invalidates_index }, { "test_copy_table_collection", test_copy_table_collection }, { "test_sort_tables_errors", test_sort_tables_errors }, + { "test_sort_tables_canonical_errors", test_sort_tables_canonical_errors }, { "test_sort_tables_canonical", test_sort_tables_canonical }, { "test_sorter_interface", test_sorter_interface }, { "test_dump_unindexed", test_dump_unindexed }, diff --git a/c/tskit/core.c b/c/tskit/core.c index 42ea25e37a..7c5dc57da8 100644 --- a/c/tskit/core.c +++ b/c/tskit/core.c @@ -281,6 +281,9 @@ tsk_strerror_internal(int err) case TSK_ERR_MUTATION_PARENT_AFTER_CHILD: ret = "Parent mutation ID must be < current ID"; break; + case TSK_ERR_MUTATION_PARENT_INCONSISTENT: + ret = "Mutation parent references form a loop."; + break; case TSK_ERR_INCONSISTENT_MUTATIONS: ret = "Inconsistent mutations: state already equal to derived state"; break; diff --git a/c/tskit/core.h b/c/tskit/core.h index da11798303..eba37ddc24 100644 --- a/c/tskit/core.h +++ b/c/tskit/core.h @@ -246,7 +246,8 @@ not found in the file. #define TSK_ERR_MUTATION_PARENT_DIFFERENT_SITE -500 #define TSK_ERR_MUTATION_PARENT_EQUAL -501 #define TSK_ERR_MUTATION_PARENT_AFTER_CHILD -502 -#define TSK_ERR_INCONSISTENT_MUTATIONS -503 +#define TSK_ERR_MUTATION_PARENT_INCONSISTENT -503 +#define TSK_ERR_INCONSISTENT_MUTATIONS -504 #define TSK_ERR_UNSORTED_MUTATIONS -505 #define TSK_ERR_NON_SINGLE_CHAR_MUTATION -506 #define TSK_ERR_MUTATION_TIME_YOUNGER_THAN_NODE -507 diff --git a/c/tskit/tables.c b/c/tskit/tables.c index d79a46d726..b9558e21bb 100644 --- a/c/tskit/tables.c +++ b/c/tskit/tables.c @@ -4905,6 +4905,10 @@ tsk_table_sorter_sort_mutations_canonical(tsk_table_sorter_t *self) p = mutations->parent[j]; while (p != TSK_NULL) { sorted_mutations[p].num_descendants += 1; + if (sorted_mutations[p].num_descendants > (int) num_mutations) { + ret = TSK_ERR_MUTATION_PARENT_INCONSISTENT; + goto out; + } p = mutations->parent[p]; } }