Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] Implement DexVerify and LTD #34

Open
wants to merge 26 commits into
base: main
Choose a base branch
from

Conversation

OCHyams
Copy link
Contributor

@OCHyams OCHyams commented Jul 12, 2019

EDIT Here's the full RFC walk through.

DexVerify and LTD

Background

Combined with #26, this patch allows us to use DExTer to verify any and all
expected program states are encountered while debugging.

The work is all based on Linear Temporal Logic (LTL). Using LTL you can verify
that a set of propositions hold over time. LTL uses a small set of base
temporal operators and boolean connectives to build up complex propositions.

While not strictly true in LTL theory, consider the following to be our base
set of operators:

Boolean
/\   - And
\/   - Or
!    - Not

Temporal:
U   - Until
W   - Weak
X   - Next

Andour atomic propositions as:

True
False
Some assertion about program state

LTL_work.md covers all the definitions of the operators, but I'll give a quick
overview here.

Until:
p U q means p must hold at least until q holds, which it must do at some point.

Weak (weak Until):
p W q means p must hold until q holds, or forever if q does not hold.

Next:
X p means p must hold in the next time frame.

Propositions can be combined with these operators to create new propositions:

p ::= a  |  p /\ p | p \/ p |  !p  |  Xp |  p U p | p W q

Where a is an atomic proposition as described above.

It becomes useful to define common formulae patterns as composite operators.
One such example is Eventually or F for future.

F a == True U a

Taking the definition I provided earlier "True (p) must hold at least until
q (a) holds, which it must do at some point". That is to say, a must
Eventually hold.

LTL to LTD

The LTL operators are exposed as subcommands (referred to as LTD) to a new
dexter command DexVerify.

Time is discrete, where each step represents a debugger step. Next(p) means
that p must hold at the next debugger step.

Use the LTD command ExpectState to make an assertion about the program state.
ExpectState behaves very similarly to the standalone DexExpectProgramState
command.

To verify an assertion that some program state is eventually encountered,
you'd write:

DexVerify(Eventually(ExpectState(/* Program state description  */)))

Implementation

The base operators are defined in BasicOperators.py. These are derived from
an operator type (binary, unary) from OperatorTypes.py, which are all in turn
derived from Proposition from Proposition.py.

Propositions and boolean connectives can only work on the program state as
described by the debugger at a specific point in time. Temporal operators,
however, need to look into the future.

To accomodate this a program state iterator DextStepIter is defined in
DextIR.py. Operators can dereference and increment the iterator before passing
it on to their operands. Any operator which must increment the iterator first
copies it so that the "future looking" is limited to only its operands.

See Until.eval(...) and Next.eval(...) for concrete examples.

Using the basic operator types as a foundation with which to build all
composite operators makes for some really compact defintions.

Composite operators, defined in CompositeOperators.py, inherit from Composite.
They must call set_proposition to define the forumla they represent. For
example, The sum of the class Eventually is as follows:

class Eventually(Composite, UnaryOperator):
    def __init__(self, *args):
        super().__init__(*args)
        self.set_proposition(Until(True, self.operand))

Remember that the definition for Eventually (F) is True U a.

This RFC defines more composite operators but hopefully you're now equipped to
look at these in source.

The examples/LTD/ directory contains some simple examples which can be run
through DExTer with this patch (xfail prefix denotes an expected failure).

Shortcomings

  1. This patch doesn't provide any nice output for --verbose. We need to implement
    a nice error reporter which can concisely explain why a given model doesn't
    hold.

  2. Exposing operators as function calls means all formulae read in prefix notation.
    This turns p Until q into Until(p, q). I personally read this left to right
    as "Until p holds, q must hold". This is incorrect. Until(p, q) means "Until q
    holds, p must hold". I find that swapping the operands makes it more readable,
    but then of course makes it confusing to people who read prefix notation naturally.
    I think this at least warrants a discussion.

  3. Prefix notation also makes some operators read strangely. I think Both(p, q) and
    Either(p, q) instead of And(p, q) and Or(p, q) respectively are more natural.

  4. The implementation doesn't do anything to prevent someone adding an operator
    with circular dependencies. Hopefully whoever implemented the new operator will
    have run tests with it and then seen a max stack depth exception.

OCHyams added 23 commits July 11, 2019 16:11
Add LTL notes outlining existing ideas and findings.
This commit contains the inital LTL implementation. It has a lot of dev-cruft
such as dev prints, ugly comments, poor programming practices etc.
This commit just aims to tidy up the arg parsing. It is a step in the right
direction but is still far from a final implementation.
These need fleshing out but for now they serve as regressions tests
while developing the LTD system.
Model holds = 0.0 (no penalty)
Model does not hold = 1.0 (full penalty)
...to 'proposition' for clarity.
Composite operators were previously printing "{name} is {underlying-formula}({
args})" which was confusing for complex models. They now print only "{name}({
args})". The full trace (when implemented) will show how the operators are
composed under the hood.

Improve LTD error messages

If the wrong number of args are given to any class inheriting from any of
the classes in OperatorTypes.py the derived class name is printed with the
error.
name is printed with the error.
Next(p): p must hold at the next time step.
This patch adds class Composite which all composite operators should inherit
from. The composite operators then only need to define the underlying
proposition which removes some boilerplate code.
After(p, q): p must hold at some point after q. Both must hold but cannot hold
simultaneously.
This allows additional globals to be passed to eval() without allowing the
user to call the subcommands globally in the test file.
The LTD commands are now no longer callable from the test files outside of the
DexVerify command.
Add checks for DextStepIter validity before dereferencing

Fix LTD After() docstring

The docstring had switched p and q when describing After's behaviour. This will
probably be a common error because of the prefix notation. This has been raised
as an issue and will be discussed in the future.

Add missing newline to CompositeOperators.py
Release operator LTL definition was using U instead of W operator.

The LTL definitions of Weak and Until are now more solid.

I've done a little rewording here and there.
Also update an incorrect TODO comment.
Ordered(p, q, ...): p comes before q [comes before ...]. The propositions may be
separated by any number of steps but they all must eventually hold.
@OCHyams
Copy link
Contributor Author

OCHyams commented Jul 12, 2019

At the moment the LTL_work document is around 200 lines, there are at least 100 lines of license comments, and there are around 150 lines of "example" test files. So, this PR is not actually as large as it looks.

@OCHyams
Copy link
Contributor Author

OCHyams commented Jul 16, 2019

I've noticed that my implementation of Next() is inconsistent.
Say we are on the last program step and are verifying Next(p). If p is an Expect or ExpectState it will not hold because the step_iter cannot be dereferenced. However, if p is simply True, it will hold because we never have to dereference the iter to find out the result of the proposition.

An easy fix: Next() should return False if the end of the program is reached after incrementing the iterator.

Copy link
Contributor

@SLTozer SLTozer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I would say something about adding unittests but...

# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""[TODO] Add words
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Words should be added before merging

@OCHyams
Copy link
Contributor Author

OCHyams commented Jul 17, 2019

Thanks @Melamoto for taking a look!

Next(p) should return False if there is no next time step. Without this patch
Next(True) would have always been evaluated to True.

Henceforth(q) == Release(False, q) == Weak(q, And(q, False))
And(q, False) is always False, so:
Weak(q, And(q, False)) == Weak(q, False)

Fixed some typos and removed an unused import.
@OCHyams
Copy link
Contributor Author

OCHyams commented Jul 19, 2019

Latest commit (afbd9ff) fixes Next, simplifies Henceforth(), and corrects some typos.
Note: I haven't added/updated the docstrings yet.

OCHyams added 2 commits July 22, 2019 11:34
Add and improve LTD in-code documentation.

External documentation will be required for the operators but the format
is yet to be decided.
BinaryOperatorTree.proposition_template() was fairly un-pythonic.

Improve DexVerify docstring.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants