fix ref_free_steal
[metze/samba/wip.git] / lib / talloc / testsuite.c
index 49070d9cbdbbd769075a60b6a2622bda22f1bdc5..8720f0dbff21d8ebfab0d0a3625a31456428757f 100644 (file)
@@ -111,6 +111,25 @@ static double timeval_elapsed(struct timeval *tv)
        } \
 } while (0)
 
+static unsigned int test_abort_count;
+
+static void test_abort_fn(const char *reason)
+{
+       printf("# test_abort_fn(%s)\n", reason);
+       test_abort_count++;
+}
+
+static void test_abort_start(void)
+{
+       test_abort_count = 0;
+       talloc_set_abort_fn(test_abort_fn);
+}
+
+static void test_abort_stop(void)
+{
+       test_abort_count = 0;
+       talloc_set_abort_fn(NULL);
+}
 
 /*
   test references 
@@ -880,7 +899,288 @@ static bool test_implicit_explicit_free(void)
 
        talloc_free(root);
 
-       printf("success: ref1\n");
+       printf("success: test_implicit_explicit_free\n");
+       return true;
+}
+
+/* If take r1 reference to p2 and then free p2's parent
+   p2 should still be around by virtue of the reference.
+   In current talloc r1 will be the parent
+   In proposed talloc r1 will be the reference with no parent */
+static bool test_ref_free_owner(void)
+{
+       void *root, *p1, *p2, *ref, *r1;
+
+       printf("test: ref_free_owner\n# SINGLE REFERENCE FREE OWNER FREE\n");
+
+       root = talloc_named_const(NULL, 0, "root");
+       p1 = talloc_named_const(root, 1, "p1");
+       p2 = talloc_named_const(p1, 1, "p2");
+       /* Now root owns p1 ,and p2 owns p2 */
+
+       r1 = talloc_named_const(root, 1, "r1");
+       ref = talloc_reference(r1, p2);
+       /* now r1 has ref reference to p2 */
+       talloc_report_full(root, stderr);
+
+       CHECK_BLOCKS(__FUNCTION__, NULL, 7);
+       CHECK_BLOCKS(__FUNCTION__, p1, 2);
+       CHECK_BLOCKS(__FUNCTION__, p2, 1);
+       CHECK_BLOCKS(__FUNCTION__, r1, 2);
+
+       fprintf(stderr, "Freeing p1\n");
+       talloc_free(p1);
+       /* r1 should have ref reference to p2 still */
+       talloc_report_full(NULL, stderr);
+       CHECK_BLOCKS(__FUNCTION__, NULL, 6);
+       CHECK_BLOCKS(__FUNCTION__, r1, 2);
+       CHECK_BLOCKS(__FUNCTION__, p2, 1);
+
+       /* if we free r1 then p2 should vanish */
+       fprintf(stderr, "Freeing r1\n");
+       talloc_free(r1);
+
+       talloc_report_full(NULL, stderr);
+       CHECK_BLOCKS(__FUNCTION__, NULL, 3);
+       CHECK_BLOCKS(__FUNCTION__, root, 1);
+
+       talloc_free(root);
+       printf("success: ref_free_owner\n");
+       return true;
+}
+
+/* If take r1 reference to p2 and then free p2
+   p2 should still be around by virtue of the reference.
+   In current talloc r1 will be the parent
+   In proposed talloc r1 will be the reference with no parent */
+static bool test_ref_free_self(void)
+{
+       void *root, *p1, *p2, *ref, *r1;
+
+       printf("test: ref_free_self\n# SINGLE REFERENCE FREE SELF FREE\n");
+
+       root = talloc_named_const(NULL, 0, "root");
+       p1 = talloc_named_const(root, 1, "p1");
+       p2 = talloc_named_const(p1, 1, "p2");
+       /* Now root owns p1, and p1 owns p2 */
+
+       r1 = talloc_named_const(root, 1, "r1");
+       ref = talloc_reference(r1, p2);
+       /* now r1 has ref reference to p2 */
+       talloc_report_full(NULL, stderr);
+
+       CHECK_BLOCKS(__FUNCTION__, NULL, 7);
+       CHECK_BLOCKS(__FUNCTION__, p1, 2);
+       CHECK_BLOCKS(__FUNCTION__, p2, 1);
+       CHECK_BLOCKS(__FUNCTION__, r1, 2);
+
+       fprintf(stderr, "Freeing p2\n");
+       talloc_free(p2);
+       /* r1 should have ref reference to p2 still */
+       talloc_report_full(NULL, stderr);
+       CHECK_BLOCKS(__FUNCTION__, NULL, 7);
+       CHECK_BLOCKS(__FUNCTION__, p1, 1);
+       CHECK_BLOCKS(__FUNCTION__, p2, 1);
+       CHECK_BLOCKS(__FUNCTION__, r1, 2);
+
+       /* if we free r1 then p2 should also vanish */
+       fprintf(stderr, "Freeing r1\n");
+       talloc_free(r1);
+
+       fprintf(stderr, "Checking that p1 is empty and freeing p1\n");
+       CHECK_BLOCKS(__FUNCTION__, NULL, 4);
+       CHECK_BLOCKS(__FUNCTION__, p1, 1);
+       talloc_free(p1);
+
+       talloc_report_full(NULL, stderr);
+       CHECK_BLOCKS(__FUNCTION__, NULL, 3);
+       CHECK_BLOCKS(__FUNCTION__, root, 1);
+
+       talloc_free(root);
+       CHECK_BLOCKS(__FUNCTION__, NULL, 2);
+       printf("success: ref_free_self\n");
+       return true;
+}
+
+/* check that an allocation that is freed while also referenced finally goes
+   away when the reference is released */
+static bool test_ref_free(void)
+{
+       void *root, *p1, *p2, *ref, *r1;
+
+       printf("test: test_ref_free\n# FREE ON SINGLE REFERENCE FREE\n");
+
+       root = talloc_named_const(NULL, 0, "root");
+       p1 = talloc_named_const(root, 1, "p1");
+       p2 = talloc_named_const(p1, 1, "p2");
+       /* Now root owns p1, and p1 owns p2 */
+
+       r1 = talloc_named_const(root, 1, "r1");
+       ref = talloc_reference(r1, p2);
+       /* now r1 has ref reference to p2 */
+       talloc_report_full(root, stderr);
+
+       CHECK_BLOCKS(__FUNCTION__, p1, 2);
+       CHECK_BLOCKS(__FUNCTION__, p2, 1);
+       CHECK_BLOCKS(__FUNCTION__, r1, 2);
+
+       fprintf(stderr, "Freeing p2\n");
+       talloc_free(p2);
+       /* r1 should have ref reference to p2 still */
+       talloc_report_full(root, stderr);
+
+       CHECK_BLOCKS(__FUNCTION__, p1, 1);
+       CHECK_BLOCKS(__FUNCTION__, root, 4);
+       CHECK_BLOCKS(__FUNCTION__, r1, 2);
+
+       /* if we free r1 then p2 should also vanish */
+       fprintf(stderr, "Freeing r1\n");
+       talloc_free(r1);
+       /* p2 should have gone away  */
+       talloc_report_full(NULL, stderr);
+       CHECK_BLOCKS(__FUNCTION__, root, 2);
+       CHECK_BLOCKS(__FUNCTION__, p1, 1);
+
+       talloc_free(root);
+       printf("success: test_ref_free\n");
+       return true;
+}
+
+/* If an object having references from children that are also referenced is
+   freed, the child reference will be removed, but the child will survive
+   (because of it's reference) and the object will still be freed leaving
+   a dangling reference */
+static bool test_dangling_loop(void)
+{
+       void *root, *p1, *p2, *ref, *r1, *r2;
+
+       printf("test: %s\n# FREE ON SINGLE REFERENCE FREE\n",__FUNCTION__);
+
+       root = talloc_named_const(NULL, 0, "root");
+       p1 = talloc_named_const(root, 1, "p1");
+       p2 = talloc_named_const(p1, 1, "p2");
+       /* Now root owns p1, and p1 owns p2 */
+
+       /* someone takes a ref on p2 */
+       r1 = talloc_named_const(root, 1, "r1");
+       ref = talloc_reference(r1, p2);
+
+       /* p2 takes a ref on p1 */
+       talloc_reference(p2, p1);
+
+       talloc_report_full(NULL, stderr);
+
+       CHECK_BLOCKS(__FUNCTION__, root, 6);
+       CHECK_BLOCKS(__FUNCTION__, p1, 3);
+       CHECK_BLOCKS(__FUNCTION__, p2, 2);
+       CHECK_BLOCKS(__FUNCTION__, r1, 2);
+
+       /* talloc will wrongly spot a loop and free p2's ref, and then p1
+          leaving a dangling pointer */
+       fprintf(stderr, "Freeing p1\n");
+       talloc_free(p1);
+       /* p1 should not get freed as it is referenced from something (a child) that won't free */
+       /* r1 should have ref reference to p2 still */
+       talloc_report_full(NULL, stderr);
+
+       CHECK_BLOCKS(__FUNCTION__, root, 3);
+       /* The ugly talloc de-child-looping code will delete p2's reference
+          leaving p2 having a dangling pointer. p2's reference should remain */
+       /* TODO change this back to 2 when the loop detection is working */
+       CHECK_BLOCKS(__FUNCTION__, p2, 1);
+       CHECK_BLOCKS(__FUNCTION__, r1, 2);
+
+       /* if we free r1 then p2 should also vanish as it is owned by something that
+          is not owned, but we can't track that yet. Once p2 vanishes it's reference
+          to p1 should vanish letting p1 vanish.
+          We can often make sub-tree's from no-owner-context, by checking when references
+          of things child-of no-owner-context die
+       */
+       fprintf(stderr, "Freeing r1\n");
+       talloc_free(r1);
+       talloc_report_full(NULL, stderr);
+       CHECK_BLOCKS(__FUNCTION__, root, 1);
+
+       talloc_free(root);
+       printf("success: %s\n", __FUNCTION__);
+
+       talloc_report_full(NULL, stderr);
+       return true;
+}
+
+/* If take r1 reference to p2 and then free p2's owner p1
+   p2 should still be around by virtue of the reference.
+   steal p2 to p3 and free p3.
+   In current talloc p2 will be freed despite the reference.
+   In proposed talloc r1 will be the reference with no parent for p2*/
+static bool test_ref_free_steal(void)
+{
+       void *root, *p1, *p2, *p3, *ref, *r1;
+
+       printf("test: ref_free_steal\n# SINGLE REFERENCE FREE SELF FREE\n");
+
+       root = talloc_named_const(NULL, 0, "root");
+       p1 = talloc_named_const(root, 1, "p1");
+       p2 = talloc_named_const(p1, 1, "p2");
+       p3 = talloc_named_const(root, 1, "p3");
+       /* Now root owns p1, and p1 owns p2 */
+
+       r1 = talloc_named_const(root, 1, "r1");
+       ref = talloc_reference(r1, p2);
+       /* now r1 has ref reference to p2 */
+       talloc_report_full(NULL, stderr);
+
+       CHECK_BLOCKS(__FUNCTION__, p1, 2);
+       CHECK_BLOCKS(__FUNCTION__, p2, 1);
+       CHECK_BLOCKS(__FUNCTION__, p3, 1);
+       CHECK_BLOCKS(__FUNCTION__, r1, 2);
+
+       fprintf(stderr, "Freeing p1\n");
+       talloc_free(p1);
+       /* r1 should have ref reference to p2 still */
+       talloc_report_full(NULL, stderr);
+       CHECK_BLOCKS(__FUNCTION__, r1, 2);
+       CHECK_BLOCKS(__FUNCTION__, p3, 1);
+
+       /* if we talloc_steal p2 to p3, we should get an abort() */
+       fprintf(stderr, "Steal p2 to p3\n");
+       test_abort_start();
+       talloc_steal(p3, p2);
+       torture_assert(__FUNCTION__,
+                      test_abort_count == 1,
+                      "talloc_steal() didn't abort");
+       test_abort_stop();
+
+       talloc_report_full(NULL, stderr);
+       CHECK_BLOCKS(__FUNCTION__, r1, 2);
+       CHECK_BLOCKS(__FUNCTION__, p3, 1);
+
+       /* but we can reference p2 to p3 */
+       fprintf(stderr, "Steal p2 to p3\n");
+       talloc_reference(p3, p2);
+
+       talloc_report_full(NULL, stderr);
+       CHECK_BLOCKS(__FUNCTION__, r1, 2);
+       CHECK_BLOCKS(__FUNCTION__, p3, 2);
+
+       /* now we free p3 and r1 should still have a reference */
+       fprintf(stderr, "free p3\n");
+       talloc_free(p3);
+       talloc_report_full(NULL, stderr);
+       CHECK_BLOCKS(__FUNCTION__, p2, 1);
+       CHECK_BLOCKS(__FUNCTION__, r1, 2);
+
+       /* if we free r1 then p2 should also vanish */
+       fprintf(stderr, "Freeing r1\n");
+       talloc_free(r1);
+
+       talloc_report_full(NULL, stderr);
+       CHECK_BLOCKS(__FUNCTION__, NULL, 3);
+       CHECK_BLOCKS(__FUNCTION__, root, 1);
+
+       talloc_free(root);
+       CHECK_BLOCKS(__FUNCTION__, NULL, 2);
+       printf("success: ref_free_steal\n");
        return true;
 }
 
@@ -1220,6 +1520,7 @@ static bool test_pool(void)
 
 static void test_reset(void)
 {
+       test_abort_stop();
        talloc_disable_null_tracking();
        talloc_enable_null_tracking();
 }
@@ -1270,6 +1571,16 @@ bool torture_local_talloc(struct torture_context *tctx)
        test_reset();
        ret &= test_implicit_explicit_free();
        test_reset();
+       ret &= test_ref_free_steal();
+       test_reset();
+       ret &= test_dangling_loop();
+       test_reset();
+       ret &= test_ref_free_owner();
+       test_reset();
+       ret &= test_ref_free_self();
+       test_reset();
+       ret &= test_ref_free();
+       test_reset();
        ret &= test_pool();
 
        if (ret) {