[cfe-commits] RangeConstraintManager

Ben Laurie benl at google.com
Fri Feb 13 09:01:52 PST 2009


On Thu, Feb 12, 2009 at 10:29 PM, Ted Kremenek <kremenek at apple.com> wrote:
>
> On Feb 12, 2009, at 5:15 AM, Ben Laurie wrote:
>
>> Once the other patches are applied, then this file should work just
>> fine. Still has debugging messages in and still no switch to enable
>> it. Will fix both soon.
>> <
>> RangeConstraintManager
>> .cpp>_______________________________________________
>> cfe-commits mailing list
>> cfe-commits at cs.uiuc.edu
>> http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits
>
>
> Hi Ben,
>
> This is looking good!  One meta-level coding style comment I thought I'd
> mention first concerns if/for statements.  Please write:
>
>  if (condition)
>
> instead of
>
>  if(condition)
>
> It's a minor thing (it's an LLVM convention), but it's good for consistency.
>  Same thing for loops.
>
> Other comments inline!
>
>>
>> #include <iostream>
>
> Please don't use iostream (if you can help it).  Not only does it's
> performance suck, but it slows down startup time:
>
> http://llvm.org/docs/CodingStandards.html#ll_iostream

OK ... how do I activate debugging (DOUT and DEBUG)?

>> #include <set>
>
> This #include is no longer needed.
>
>> using namespace clang;
>>
>> namespace { class VISIBILITY_HIDDEN ConstRange {}; }
>>
>> static int ConstRangeIndex = 0;
>>
>> /* A Range represents the closed range [from, to].
>>  * The caller must guarantee that from <= to.
>>  */
>
> Please use C++ style comments (preferably doxygen style comments) instead of
> C-style comments.
>
>> class Range : public std::pair<llvm::APSInt, llvm::APSInt> {
>> public:
>>  Range(const llvm::APSInt &from, const llvm::APSInt &to)
>>    : std::pair<llvm::APSInt, llvm::APSInt>(from, to) {
>>    assert(from <= to);
>>  }
>>  bool Includes(const llvm::APSInt &v) const {
>>    return first <= v && v <= second;
>>  }
>>  const llvm::APSInt &From() const {
>>    return first;
>>  }
>>  const llvm::APSInt &To() const {
>>    return second;
>>  }
>>  const llvm::APSInt *HasConcreteValue() const {
>>    return From() == To() ? &From() : NULL;
>>  }
>>
>>  void Profile(llvm::FoldingSetNodeID &ID) const {
>>    From().Profile(ID);
>>    To().Profile(ID);
>>  }
>> };
>
> Looks good.  I would also add a comment to 'Range' indicating that it is
> immutable (just to make it super clear).  Its great that all the methods are
> const.  You can also probably do private inheritance from std::pair<> so
> that 'first' and 'second' are not publicly accessible.

Apparently that breaks STL stuff.

>> struct RangeCmp {
>>  bool operator()(const Range &r1, const Range &r2) {
>>    if(r1.From() < r2.From()) {
>>      assert(!r1.Includes(r2.From()));
>>      assert(!r2.Includes(r1.To()));
>>      return true;
>>    } else if(r1.From() > r2.From()) {
>>      assert(!r1.Includes(r2.To()));
>>      assert(!r2.Includes(r1.From()));
>>      return false;
>>    } else
>>      assert(!"Ranges should never be equal in the same set");
>>  }
>> };
>
> Nothing uses this anymore.

Very true.

>> typedef llvm::ImmutableSet<Range> PrimRangeSet;
>
> Looks good.  Note that ImmutableSet uses the trait class 'ImutContainerInfo'
> to compare items in the set.  The default implementation of
> ImutContainerInfo uses std::less to compare items.  More info in
> ImmutableSet.h (the default trait implementations are at the bottom of the
> file, right before the declaration of 'class ImmutableSet'.  If things are
> working correctly, you might need to migrate your logic from RangeCmp over a
> ImutContainerInfo<Range> class

I wonder what std::less does on a std::pair?

>> class RangeSet;
>> std::ostream &operator<<(std::ostream &os, const RangeSet &r);
>
> Please use llvm::cerr or llvm::raw_ostream instead.  There are pieces of the
> analysis library that still use ostream; these are getting gradualyl
> replaced with llvm::raw_ostream.

Well, I am confused. If I try to make this change, then I get an error
that requires me to implement the original code (i.e. the error is
that there's no operator<<(std::ostream, RangeSet)).

>> /*
>>  * A RangeSet contains a set of ranges. If the set is empty, then
>>  *   noValues -> Nothing matches.
>>  *  !noValues -> Everything (in range of the bit representation) matches.
>>  */
>
> C++ style comments (see other code throughout the codebase for the
> convention).  The 'AST' library is particularly well document (e.g.,
> 'include/clang/AST/Stmt.h').  I'll admit that much of the analyzer code
> isn't as well documented as it should be.  I'm not trying to be a hypocrite;
> that code needs to be fixed as well.
>
>> class RangeSet {
>>  PrimRangeSet ranges; // no need to make const, since it is an
>>                       // ImmutableSet - this allows default operator=
>>                       // to work.
>>  bool noValues;  // if true, no value is possible (should never happen)
>>  static PrimRangeSet::Factory factory;
>
> Please don't use a static member variable.  I see this as the same vice as
> using a global variable.

Hmmph. Singletons?

> The factory object's lifetime should be constrained by the lifetime of the
> enclosing RangeConstraintManager object.  Making it static also means that
> multiple RangeConstraintManagers will see the same factory object, which
> shouldn't be the case (at least at this time).  I know this means the code
> might need to be reorganized a little, but probably a better spot for the
> factory object is in RangeConstraintManager as a normal member variable.

Sure thing. It was clear to me that using a static was wrong, but it
was not clear to me what the lifetime should be.

> If you want to bring the factory object around, consider a design similar to
> GRState and GRStateRef.  GRStateRef represents a "fat reference" that brings
> around the GRStateManager object for use with GRState methods, and GRState
> just represents the raw data.
>
>
>>
>>  static const llvm::APSInt Max(const llvm::APSInt &v) {
>>    return llvm::APSInt::getMaxValue(v.getBitWidth(), v.isUnsigned());
>>  }
>>  static const llvm::APSInt Min(const llvm::APSInt &v) {
>>     return llvm::APSInt::getMinValue(v.getBitWidth(), v.isUnsigned());
>>  }
>>  static const llvm::APSInt One(const llvm::APSInt &v) {
>>    return llvm::APSInt(llvm::APInt(v.getBitWidth(), 1), v.isUnsigned());
>>  }
>
> I suppose these methods could be declared as const.

They are static!

>  Actually, it might be
> worth just putting these into APSInt directly.  It seems like something that
> other people could use.

OK.

>> public:
>>  RangeSet() : ranges(factory.GetEmptySet()),
>>               noValues(false) {
>>  }
>
> Please add a comment explaining the comments of the default constructor.
>
>>  // Note that if the empty set is passed, then there are no possible
>> values.
>>  // To create a RangeSet that covers all values, use the other
>> constructor.
>
> Is the "other construct" the default constructor?  There are three
> constructors.
>
>>  RangeSet(const PrimRangeSet &r) : ranges(r), noValues(r.isEmpty()) {
>>  }
>>  // Allow an empty set to be passed without setting noValues
>
> This comment is a little cryptic.  Isn't 'noValues' an implementation
> detail?
>
>>  RangeSet(const PrimRangeSet &r, bool n) : ranges(r), noValues(n) {
>>    assert(!n);
>>  }
>
>
>>  void Profile(llvm::FoldingSetNodeID &ID) const {
>>    ranges.Profile(ID);
>>    ID.AddBoolean(noValues);
>>  }
>
> Looks great.

As previously discussed, though, "Profile" is a really bad name for
this function!

>>  const llvm::APSInt *HasConcreteValue() const {
>>    if(!ranges.isSingleton())
>>      return NULL;
>>    return ranges.begin()->HasConcreteValue();
>>  }
>
> Looks great.  I'm a little concerned about the assumptions clients can make
> on the returned "llvm::APSInt*" object.  It's lifetime is bound to the
> lifetime of the Range object (which is bound to the ImmutableSet).  In many
> cases through the analyzer with use the "BasicValueManager" to unique APSInt
> objects so that they can be compared using direct pointer comparison.  It
> also takes care of some weird memory management issues.
>
> By default, the destructors of the objects allocated by an
> ImmutableSet::Factory are not called.  APSInt actually can 'malloc()'
> memory, meaning that if their destructors aren't called then this memory
> will just get leaked.  Using BasicValueManager solves this problem since it
> manually calls the destructors of the uniqued APSInt objects.

AFAICS, BasicConstraintManager::getSymVal, which does the same thing,
does not use BasicValueManager...

>
>>
>>  bool CouldBeNE(const llvm::APSInt &ne) const {
>>    std::cerr << "CouldBeNE(" << ne.toString(10) << ") " << *this <<
>> std::endl;
>>    assert(!noValues);
>>    if(ranges.isEmpty())
>>      return true;
>>    for(PrimRangeSet::iterator i = ranges.begin() ; i != ranges.end() ;
>> ++i)
>>      if(i->Includes(ne))
>>        return false;
>>    return true;
>>  }
>
> Just for my education, what is the purpose of 'noValues'?

noValues means that the range represents the empty set. There is a
comment against its declaration.

It is my belief that we should never be asked if an empty range set
could match any criterion - because the only way it can get into that
state is by ruling out all possible values, which is surely
impossible?

>  A small comment
> here would also be useful.  I gather the idea that this method returns true
> if the set of values represented by the range set *may* be less than 'ne'.
>  If that is the case, please put that as a comment above the function.

I realised earlier today that this implementation was incorrect. It
should return true if it is possible for the value to be != ne, and so
the correct implementation is this:

  bool CouldBeNE(const llvm::APSInt &ne) const {
    llvm::cerr << "CouldBeNE(" << ne.toString(10) << ") " << *this << std::endl;
    assert(!noValues);
    const llvm::APSInt *v = HasConcreteValue();
    if (v && *v == ne)
	return false;
    return true;
  }

 >
> I see that most of the rest of the CouldBeXXX functions is just boilerplate.
>  Looks good for starters (i.e., some obvious FIXME's are in place).   You
> may also consider adding assertions (i.e., assert(0 && "Not implemented")
> for the CouldBeXXX methods that are incomplete.'
>
>>
>>  // Make all existing ranges fall within this new range
>>  RangeSet Restrict(const llvm::APSInt &from, const llvm::APSInt &to) {
>>    if(ranges.isEmpty())
>>      return factory.Add(ranges, Range(from, to));;
>
> Looks good.

This one is not actually used, but I guess it would make sense for
dealing with casts?

>>    PrimRangeSet newRanges = factory.GetEmptySet();
>>
>>    for(PrimRangeSet::iterator i = ranges.begin() ; i != ranges.end() ;
>> ++i) {
>>      if(i->Includes(from)) {
>>        if(i->Includes(to)) {
>>          newRanges = factory.Add(newRanges, Range(from, to));
>>        } else {
>>          newRanges = factory.Add(newRanges, Range(from, i->To()));
>>        }
>>      } else if(i->Includes(to)) {
>>        newRanges = factory.Add(newRanges, Range(i->From(), to));
>>      }
>>    }
>>    return RangeSet(newRanges);
>>  }
>
> Quick question: so the idea of 'Restrict' is to do a projection of what
> ranges are possible (within the given range)?  Looks great to me.

Yes.

> BTW, it looks like 'RangeSet' is basically POD and functional.  Is it
> possible to make 'Restrict' a const?

Done.

>
>>
>>  RangeSet AddEQ(const llvm::APSInt &eq) {
>>    std::cerr << "AddEQ(" << eq.toString(10) << ") " << *this << " -> ";
>>    assert(CouldBeEQ(eq));
>>    RangeSet r(factory.Add(factory.GetEmptySet(), Range(eq, eq)));
>>    std::cerr << r << std::endl;
>>    return r;
>>  }
>
> I see, this adds the constraint that the range is equal to the given value.
>  Could you add this as a comment to the method?  BTW, is the precondition
> for this method that 'CouldBeEQ' be checked *before* calling this method?

Yes.

> An alternate design is to allow the client to just add constraints, and have
> a special RangeSet representing "no feasible value" (I think this is what
> your 'noValues' variable is for).

I still think that this should never happen, I just added it for completeness.

> Methods like 'AddEQ' could then call
> 'CouldBeEq' first to determine if the condition is feasible and if it isn't
> return the special RangeSet for "no feasible value".

Oh, I see. Hmmm. Not sure this would make for greater clarity, though.

>>  RangeSet AddNE(const llvm::APSInt &ne) {
>>    std::cerr << "AddNE(" << ne.toString(10) << ") " << *this << " -> ";
>>
>>    const llvm::APSInt max = Max(ne);
>>    const llvm::APSInt min = Min(ne);
>>    const llvm::APSInt one = One(ne);
>>
>>    PrimRangeSet newRanges = factory.GetEmptySet();
>>
>>    if(ranges.isEmpty()) {
>>      if(ne != max)
>>        newRanges = factory.Add(newRanges, Range(ne + one, max));
>>      if(ne != min)
>>        newRanges = factory.Add(newRanges, Range(min, ne - one));
>>      RangeSet r(newRanges);
>>      std::cerr << r << std::endl;
>>      return r;
>>    }
>
> Looks good.
>
>>
>>    for(PrimRangeSet::iterator i = ranges.begin() ; i != ranges.end() ;
>> ++i) {
>>      if(i->Includes(ne)) {
>>        if(ne != i->From())
>>          newRanges = factory.Add(newRanges, Range(i->From(), ne - one));
>>        if(ne != i->To())
>>          newRanges = factory.Add(newRanges, Range(ne + one, i->To()));
>>      } else {
>>        newRanges = factory.Add(newRanges, *i);
>>      }
>>    }
>
> I'm wondering if the two cases of 'ranges.isEmpty()' and this loop could be
> coalesced by representing the range of "all values" with the explicit range
> of 'Range(min, max)'.  It would probably simplify a bunch of code.

I did consider doing it this way, but the problem is that when the
RangeSet is created we don't know the width/signedness of the variable
(well, we probably do, but that information is not currently
supplied). But I agree, it might be better.

>>  RangeSet AddLT(const llvm::APSInt &lt) {
>>    std::cerr << "AddLT(" << lt.toString(10) << ") " << *this << " -> ";
>>    const llvm::APSInt min = Min(lt);
>>    const llvm::APSInt one = One(lt);
>>
>>    if(ranges.isEmpty()) {
>>      PrimRangeSet pr = factory.GetEmptySet();
>>      if(lt != min)
>>        pr = factory.Add(pr, Range(min, lt - one));
>>      RangeSet r(pr, false);
>>      std::cerr << r << std::endl;
>>      return r;
>>    }
>>
>>    PrimRangeSet newRanges = factory.GetEmptySet();
>>
>>    for(PrimRangeSet::iterator i = ranges.begin() ; i != ranges.end() ;
>> ++i) {
>>      if(i->Includes(lt) && i->From() < lt)
>>        newRanges = factory.Add(newRanges, Range(i->From(), lt - one));
>>      else if(i->To() < lt)
>>        newRanges = factory.Add(newRanges, *i);
>>    }
>>    RangeSet r(newRanges);
>>    std::cerr << r << std::endl;
>>    return r;
>>  }
>
> Again, I'm thinking that the two cases of 'ranges.isEmpty()' and the second
> case can be combined together.  Come to think of it, maybe the 'isEmpty'
> case could represent the infeasible case?  That is, if there are no ranges
> in the RangeSet, then no value is feasible.  In that scenario, I don't think
> you need the CouldBeXXX methods.  Just use the 'AddXXX' methods, and if the
> returned RangeSet is empty then you know the constraint isn't feasible.
>  That would remove a bunch of potentially redundant logic.

How about we get it checked in like this and add some tests and then rework it?

>
>>
>>  RangeSet AddLE(const llvm::APSInt &le) {
>>    std::cerr << "AddLE(" << le.toString(10) << ") " << *this << " -> ";
>>    const llvm::APSInt min = Min(le);
>>
>>    if(ranges.isEmpty()) {
>>      RangeSet r(factory.Add(ranges, Range(min, le)));
>>      std::cerr << r << std::endl;
>>      return r;
>>    }
>>
>>    PrimRangeSet newRanges = factory.GetEmptySet();
>>
>>    for(PrimRangeSet::iterator i = ranges.begin() ; i != ranges.end() ;
>> ++i) {
>>      // Strictly we should test for includes le + 1, but no harm is
>>      // done by this formulation
>>      if(i->Includes(le))
>>        newRanges = factory.Add(newRanges, Range(i->From(), le));
>>      else if(i->To() <= le)
>>        newRanges = factory.Add(newRanges, *i);
>>    }
>>    RangeSet r(newRanges);
>>    std::cerr << r << std::endl;
>>    return r;
>>  }
>>
>>  RangeSet AddGT(const llvm::APSInt &gt) {
>>    std::cerr << "AddGT(" << gt.toString(10) << ") " << *this << " -> ";
>>    const llvm::APSInt max = Max(gt);
>>    const llvm::APSInt one = One(gt);
>>
>>    if(ranges.isEmpty()) {
>>      RangeSet r(factory.Add(ranges, Range(gt + one, max)));
>>      std::cerr << r << std::endl;
>>      return r;
>>    }
>>
>>    PrimRangeSet newRanges = factory.GetEmptySet();
>>
>>    for(PrimRangeSet::iterator i = ranges.begin() ; i != ranges.end() ;
>> ++i) {
>>      if(i->Includes(gt) && i->To() > gt)
>>        newRanges = factory.Add(newRanges, Range(gt + one, i->To()));
>>      else if(i->From() > gt)
>>        newRanges = factory.Add(newRanges, *i);
>>    }
>>    RangeSet r(newRanges);
>>    std::cerr << r << std::endl;
>>    return r;
>>  }
>>
>>  RangeSet AddGE(const llvm::APSInt &ge) {
>>    std::cerr << "AddGE(" << ge.toString(10) << ") " << *this << " -> ";
>>    const llvm::APSInt max = Max(ge);
>>
>>    if(ranges.isEmpty()) {
>>      RangeSet r(factory.Add(ranges, Range(ge, max)));
>>      std::cerr << r << std::endl;
>>      return r;
>>    }
>>
>>    PrimRangeSet newRanges = factory.GetEmptySet();
>>
>>    for(PrimRangeSet::iterator i = ranges.begin() ; i != ranges.end() ;
>> ++i) {
>>      // Strictly we should test for includes ge - 1, but no harm is
>>      // done by this formulation
>>      if(i->Includes(ge))
>>        newRanges = factory.Add(newRanges, Range(ge, i->To()));
>>      else if(i->From() >= ge)
>>        newRanges = factory.Add(newRanges, *i);
>>    }
>>
>>    RangeSet r(newRanges);
>>    std::cerr << r << std::endl;
>>    return r;
>>  }
>>
>>  void Print(std::ostream &os) const {
>>    os << "{ ";
>>    if(noValues) {
>>      os << "**no values** }";
>>      return;
>>    }
>>    for(PrimRangeSet::iterator i = ranges.begin() ; i != ranges.end() ;
>> ++i) {
>>      if(i != ranges.begin())
>>        os << ", ";
>>      os << '[' << i->From().toString(10) << ", " << i->To().toString(10)
>>         << ']';
>>    }
>>    os << " }";
>>
>> }
>>  bool operator==(const RangeSet &other) const {
>>    return ranges == other.ranges;
>>  }
>> };
>>
>> PrimRangeSet::Factory RangeSet::factory;
>>
>> std::ostream &operator<<(std::ostream &os, const RangeSet &r) {
>>  r.Print(os);
>>  return os;
>> }
>>
>> typedef llvm::ImmutableMap<SymbolRef,RangeSet> ConstRangeTy;
>>
>> namespace clang {
>> template<>
>> struct GRStateTrait<ConstRange> : public GRStatePartialTrait<ConstRangeTy>
>> {
>>  static inline void* GDMIndex() { return &ConstRangeIndex; }
>> };
>> }
>>
>> namespace {
>> class VISIBILITY_HIDDEN RangeConstraintManager : public
>> SimpleConstraintManager {
>> public:
>>  RangeConstraintManager(GRStateManager& statemgr)
>>      : SimpleConstraintManager(statemgr) {}
>>
>>  const GRState* AssumeSymNE(const GRState* St, SymbolRef sym,
>>                             const llvm::APSInt& V, bool& isFeasible);
>>
>>  const GRState* AssumeSymEQ(const GRState* St, SymbolRef sym,
>>                                const llvm::APSInt& V, bool& isFeasible);
>>
>>  const GRState* AssumeSymLT(const GRState* St, SymbolRef sym,
>>                                    const llvm::APSInt& V, bool&
>> isFeasible);
>>
>>  const GRState* AssumeSymGT(const GRState* St, SymbolRef sym,
>>                             const llvm::APSInt& V, bool& isFeasible);
>>
>>  const GRState* AssumeSymGE(const GRState* St, SymbolRef sym,
>>                             const llvm::APSInt& V, bool& isFeasible);
>>
>>  const GRState* AssumeSymLE(const GRState* St, SymbolRef sym,
>>                             const llvm::APSInt& V, bool& isFeasible);
>>
>>  const GRState* AssumeInBound(const GRState* St, SVal Idx, SVal
>> UpperBound,
>>                               bool Assumption, bool& isFeasible);
>>
>>  const GRState* AddEQ(const GRState* St, SymbolRef sym, const
>> llvm::APSInt& V);
>>
>>  const GRState* AddNE(const GRState* St, SymbolRef sym, const
>> llvm::APSInt& V);
>>
>>  const GRState* AddLT(const GRState* St, SymbolRef sym, const
>> llvm::APSInt& V);
>>
>>  const GRState* AddLE(const GRState* St, SymbolRef sym, const
>> llvm::APSInt& V);
>>
>>  const GRState* AddGT(const GRState* St, SymbolRef sym, const
>> llvm::APSInt& V);
>>
>>  const GRState* AddGE(const GRState* St, SymbolRef sym, const
>> llvm::APSInt& V);
>
> I'm wondering if methods like 'AddGT' could just be AssumeSymGE?  They
> basically have the same interface other than the 'isFeasible' flag.  I
> suppose they should be kept separate for cleanliness, although they look
> practically identical.
>
> One possibility is to have methods like 'AddGT' reason about RangeSets (and
> only RangeSets) and have AssumeSymGT reason about states.  This would
> provide a nice clean separation of logic.

Right.

>
>>
>>  // FIXME: these two are required because they are pure virtual, but
>>  // are they useful with ranges? Neither is used in this file.
>>  const llvm::APSInt* getSymVal(const GRState* St, SymbolRef sym) const;
>>  bool isEqual(const GRState* St, SymbolRef sym, const llvm::APSInt& V)
>> const;
>
> These methods are still meaningful on the context of ranges, although the
> clients of these methods should potentially be thought out a little bit more
> to determine if these queries are still the right ones.

isEqual() is used, I think, to test for things like divide-by-zero.
But surely CouldBeEQ() is actually more appropriate?

>
>>
>> const GRState*
>> RangeConstraintManager::AssumeSymNE(const GRState* St, SymbolRef sym,
>>                                    const llvm::APSInt& V, bool&
>> isFeasible) {
>>  isFeasible = CouldBeNE(St, sym, V);
>>  if (isFeasible)
>>    return AddNE(St, sym, V);
>>  return St;
>> }
>
> Looks good, but the more I think about it the more I think that CouldBeNE
> and AddNE (and friends) can be merged.  It would probably be far more
> efficient (no double looping through the ranges).

Yeah, but see above.

>> /// Scan all symbols referenced by the constraints. If the symbol is not
>> alive
>> /// as marked in LSymbols, mark it as dead in DSymbols.
>> const GRState*
>> RangeConstraintManager::RemoveDeadBindings(const GRState* St,
>>                                           SymbolReaper& SymReaper) {
>>  GRStateRef state(St, StateMgr);
>>
>>  ConstRangeTy CR = state.get<ConstRange>();
>>  ConstRangeTy::Factory& CRFactory = state.get_context<ConstRange>();
>>
>>  for (ConstRangeTy::iterator I = CR.begin(), E = CR.end(); I != E; ++I) {
>>    SymbolRef sym = I.getKey();
>>    if (SymReaper.maybeDead(sym)) CR = CRFactory.Remove(CR, sym);
>>  }
>>
>>  return state.set<ConstRange>(CR);
>> }
>
> Looks good.
>
>
>>
>> void RangeConstraintManager::print(const GRState* St, std::ostream& Out,
>>                                   const char* nl, const char *sep) {
>>  /*
>>  // Print equality constraints.
>>
>>  ConstEqTy CE = St->get<ConstEq>();
>>
>>  if (!CE.isEmpty()) {
>>    Out << nl << sep << "'==' constraints:";
>>
>>    for (ConstEqTy::iterator I = CE.begin(), E = CE.end(); I!=E; ++I) {
>>      Out << nl << " $" << I.getKey();
>>      llvm::raw_os_ostream OS(Out);
>>      OS << " : "   << *I.getData();
>>    }
>>  }
>>
>>  // Print != constraints.
>>
>>  ConstNotEqTy CNE = St->get<ConstNotEq>();
>>
>>  if (!CNE.isEmpty()) {
>>    Out << nl << sep << "'!=' constraints:";
>>
>>    for (ConstNotEqTy::iterator I = CNE.begin(), EI = CNE.end(); I!=EI;
>> ++I) {
>>      Out << nl << " $" << I.getKey() << " : ";
>>      bool isFirst = true;
>>
>>      GRState::IntSetTy::iterator J = I.getData().begin(),
>>                                  EJ = I.getData().end();
>>
>>      for ( ; J != EJ; ++J) {
>>        if (isFirst) isFirst = false;
>>        else Out << ", ";
>>
>>        Out << (*J)->getSExtValue(); // Hack: should print to raw_ostream.
>>      }
>>    }
>>  }
>>  */
>>  Out << nl << "Implement range printing";
>> }
>>
>
> It's fine not to implement this for now.  Please use '#if 0' instead of a
> block comment so its more apparent that its disabled (that's the
> convention).  '#if 0' is also safer than nested comments.

Sure.

New version coming soon...



More information about the cfe-commits mailing list