Z3Prover / z3

The Z3 Theorem Prover
Other
9.96k stars 1.46k forks source link

reading string value from the output of z3 consequence API throws exception #7237

Closed chauhansantosh17 closed 3 weeks ago

chauhansantosh17 commented 1 month ago

Recently, we have started consuming consequence API from z3 solver class. We are getting some unexpected behaviour from get_string API when it is invoked on z3 expr returned by the consequence API. Let's consider the c++ code snippet that demonstrate the problem

void testConsequenceAPI() { context c; // declare z3 context //declare bool constants expr a = c.bool_const("a"); expr b = c.bool_const("b"); expr d = c.bool_const("d"); expr x = c.int_const("x"); // declare integer constant expr y = c.string_const("y"); // declare string constant

solver s(c); // create a default z3 solver object from the context
expr c1 = c.bool_const("c1");
s.add( c1 == (x > 0 & x < 4)); // this assert limit value of x to be between 0 and 4
expr c2 = c.bool_const("c2");
expr stringValue = c.string_val("ABC");
s.add(c2 == implies(a, (x == 1 & y == stringValue))); // when c2 is true, then if a is true then x should be 1 and y should be "ABC"
expr c3 = c.bool_const("c3");
expr stringValue2 = c.string_val("XYZ");
s.add(c3 == implies(b, (x == 2 & y == stringValue2))); // when c3 is true, then if b is true then x should be 2 and y should be "XYZ"
expr c4 = c.bool_const("c4");
s.add(c4 == implies(a, d)); // when c4 is true, then if a is true then d should be true

expr_vector inputConstraints(c);
inputConstraints.push_back(c1);
inputConstraints.push_back(c2); // this would enforce x=1 & y ="ABC"
inputConstraints.push_back(c3); // this would enforce b =false
inputConstraints.push_back(c4); // this would enforce d = true
inputConstraints.push_back(a); // this would enforce a=true

expr_vector familiesToExpand(c);
familiesToExpand.push_back(x);
familiesToExpand.push_back(y);
familiesToExpand.push_back(b);
familiesToExpand.push_back(d);

expr_vector expandedFamilies(c);
**z3::check_result consequenceResult = s.consequences(inputConstraints, familiesToExpand, expandedFamilies);**
if(consequenceResult == z3::sat)
{
    // the below line prints the below output
    /*
    //(ast-vector
    //(=> (and c4 a) d)
    //(=> (and c2 a)
    //    (= y
    //       (str.++ (seq.unit (_ Char 65))
    //               (seq.unit (_ Char 66))
    //               (seq.unit (_ Char 67)))))
    //(=> (and c2 a) (= x 1))
    //(=> (and c2 c3 a) (not b)))
    /**/
    std::cout << "consequence result: " << expandedFamilies << "\n";

    // process the above output on at a time in each iteration of the below loop
    std::for_each(expandedFamilies.begin(), expandedFamilies.end(),
        [](const z3::expr& currentConsequence)
        {
            if(currentConsequence.is_implies()) // every expr is a implies in output vector from consequence API
            {
                const z3::expr& antecedent = currentConsequence.arg(0);
                const z3::expr& consequent = currentConsequence.arg(1);
                std::cout << "Constraint responsible for current expansion: " << antecedent << "\n";
                if(consequent.is_bool()) // every consequent expr is of bool sort
                {
                    const size_t argCount = consequent.num_args();
                    std::cout << "Consequent: " << consequent << "\n";
                    if(argCount == 0)
                    {
                        // it mean this is boolean family which is assigned true in consequence result
                        // 'd' boolean constant is assigned true in consequence result
                        std::cout << "Current Family Name: " << consequent.decl().name().str() << ". Solution:True" << "\n";
                    }
                    else if(argCount == 1)
                    {
                        // 'b' boolean constant is assigned false in consequence result
                        std::cout << "Current Family Name: " << consequent.arg(0).decl().name().str() << "." << "\n";
                        Z3_decl_kind currentResultOperation = consequent.decl().decl_kind();
                        if(Z3_OP_NOT == currentResultOperation)
                        {
                            std::cout << "boolean family is assigned false in consequence result\n";
                        }

                    }
                    else if(argCount == 2)
                    {
                        const z3::expr& family = consequent.arg(0); // this would either family varible - x or Y
                        std::cout << "Current Family: " << family.decl().name().str() << ". Sort Type:" << family.get_sort().sort_kind() << "\n";
                        if(family.get_sort().is_int())
                        {
                            int64_t value = 0;
                            if(consequent.arg(1).is_numeral_i64(value)) // this get integer value assigned to x family in consequence result
                            {
                                std::cout << "Current assignment: " << value << "." << "\n";
                            }
                        }
                        else if(family.get_sort().sort_kind() == Z3_SEQ_SORT) // string is considered as sequence of characters
                        {
                            z3::sort currentSeq = consequent.arg(1).get_sort(); // arg(1) is the value assigned to string family 'y' in consequence result
                            if(currentSeq.is_seq())
                            {
                                Z3_sort_kind seqSortType = currentSeq.sort_kind();
                                std::cout << "Current assignment is seq sort: " << seqSortType << std::endl;

                                // My understanding is at this point consequent.arg(1) holds the below information:
                                //       (str.++ (seq.unit (_ Char 65))
                                //               (seq.unit (_ Char 66))
                                //               (seq.unit (_ Char 67)))))
                                **std::cout << "Current assignment value:" << consequent.arg(1).get_string() << std::endl;**
                            }
                        }
                    }
                    else
                    {
                        std::cout << "Consequent: " << consequent << "\n";
                    }
                }
                else
                {
                    std::cout << "Consequent: " << consequent << "\n";
                }
            }
        });
}
else
{
    std::cout << "unsat\n";
    expr_vector core = s.unsat_core();
    std::cout << "core size: " << core << "\n";
}

}

The get_string API in the above highlighted line throw exception terminating the program. My expectation is: the get_string API should return a string containing "ABC", considering the assumption expr(inputConstraints) for the consequence API?

Am I missing something? Could you please help to understand why the above get_string API throw expection? What corrective action we can take to fix the exception and to get "ABC" as the return string?

NikolajBjorner commented 3 weeks ago

pre-processing converts string literals into a concatenation of characters. The output of consequence finding has been pre-processed. It is semantically equal to a string literal, but the get_string() API only works for string literals.