From de1b48a013b6f4bdf68f7edf333db63a0994ef20 Mon Sep 17 00:00:00 2001
From: John Lee <jjl@pobox.com>
Date: Wed, 25 Oct 2017 22:17:47 +0100
Subject: [PATCH] Fix bug where .total used in value exprs breaks totals

* Re-initialize (to VOID) totals for the account and its ancestors on adding
  postings.  Otherwise the cache intended for use by recursive calls of C++
  function total() in computing family (i.e. account hierarchy) totals is
  incorrectly retained from one top-level call to the next, causing
  inconsistent and broken behaviour.

* Re-initialize (to false) calculated and gathered.  Otherwise we won't
  e.g. recalculate stale totals for ancestor accounts (e.g. won't recalculate
  Assets:Savings total if Assets:Savings changes via a posting).

Although the value expression total function is used by ledger itself in
computing totals, this bug would only appear on use of .total in user-supplied
value expressions computed *during parsing* of ledger files, rather than after
parsing (I believe ledger only ever calls it for internal purposes after
parsing is complete).

It is possible this bug also affected other functions than total (perhaps even
in circumstances other than analagous to that described in the preceding
paragraph).  I have not checked that.
---
 src/account.cc            | 13 +++++++++++++
 test/regress/total-1.test | 28 ++++++++++++++++++++++++++++
 test/regress/total-2.test | 30 ++++++++++++++++++++++++++++++
 3 files changed, 71 insertions(+)
 create mode 100644 test/regress/total-1.test
 create mode 100644 test/regress/total-2.test

diff --git a/src/account.cc b/src/account.cc
index 14e8b0a96..da8b0b8ef 100644
--- a/src/account.cc
+++ b/src/account.cc
@@ -136,6 +136,19 @@ void account_t::add_post(post_t * post)
     xdata_->self_details.calculated   = false;
     xdata_->family_details.gathered   = false;
     xdata_->family_details.calculated = false;
+    if (! xdata_->family_details.total.is_null()) {
+      xdata_->family_details.total = ledger::value_t();
+    }
+    account_t *ancestor = this;
+    while (ancestor->parent) {
+      ancestor = ancestor->parent;
+      if (ancestor->has_xdata()) {
+        xdata_t &xdata = ancestor->xdata();
+        xdata.family_details.gathered   = false;
+        xdata.family_details.calculated = false;
+        xdata.family_details.total = ledger::value_t();
+      }
+    }
   }
 }
 
diff --git a/test/regress/total-1.test b/test/regress/total-1.test
new file mode 100644
index 000000000..51448dd2b
--- /dev/null
+++ b/test/regress/total-1.test
@@ -0,0 +1,28 @@
+; Test that calling total does not affect future calls to that function via a
+; stale cache entry for the totalled account, because of a change to that same
+; account
+
+2017-10-01 * Opening Balance
+    Assets:Current Account                  $1000.00
+    Equity:Opening Balances
+
+2017-10-02 * Savings
+    Assets:Savings                           $100.00
+    Assets:Current Account
+
+assert account("Assets:Current Account").total == $900.00
+
+2017-10-03 * Savings
+    Assets:Savings                           $100.00
+    Assets:Current Account
+
+assert account("Assets:Current Account").total == $800.00
+
+test bal
+            $1000.00  Assets
+             $800.00    Current Account
+             $200.00    Savings
+           $-1000.00  Equity:Opening Balances
+--------------------
+                   0
+end test
diff --git a/test/regress/total-2.test b/test/regress/total-2.test
new file mode 100644
index 000000000..2b15650f2
--- /dev/null
+++ b/test/regress/total-2.test
@@ -0,0 +1,30 @@
+; Test that calling total does not affect future calls to that function via a
+; stale cache entry for the totalled account, because of a change to an
+; descendant (Assets:Current:Unallocated) of that account (Assets:Current)
+
+2017-10-01 * Opening entry
+    Assets:Current:Unallocated              $2000.00
+    Equity:Opening Balances
+
+2017-10-02 * Initial savings
+    Assets:Current:Savings                  $1000.00
+    Assets:Current:Unallocated
+
+assert account("Assets:Current").total == $2000.00
+
+2017-10-03 * Balance
+    Assets:Current:Unallocated                 $1.00
+    Equity:Adjustments
+
+assert account("Assets:Current").total == $2001.00
+
+test bal
+            $2001.00  Assets:Current
+            $1000.00    Savings
+            $1001.00    Unallocated
+           $-2001.00  Equity
+              $-1.00    Adjustments
+           $-2000.00    Opening Balances
+--------------------
+                   0
+end test
