[analyzer] Evaluating a call to operator bool()

classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

[analyzer] Evaluating a call to operator bool()

Hubert Tong via cfe-dev
Hi list,

I have the following code to analyze:

struct BoolConvertibleStruct {
    int n;
    BoolConvertibleStruct(int m) : n(m) {}
    operator bool() const { return n != 0; }
};

BoolConvertibleStruct StructFunc() {
    return 1;
}

I have reduced my problem to wanting to analyze StructFunc() to figure out the truth value of its return value. (The actual problem is more complicated and you might recognize that BoolConvertibleStruct is a stand-in for std::unique_ptr<T>, among other things :-P)

I have the following sample checker:

class Analyzer : public Checker<check::EndFunction> {
 public:
    void checkEndFunction(const ReturnStmt* ret, CheckerContext& cx) const {
        const auto* func = cast<FunctionDecl>(cx.getStackFrame()->getDecl());
        if (func->getQualifiedNameAsString() != "StructFunc")
            return;

        ProgramStateRef state = cx.getState();
        SValBuilder& builder = cx.getSValBuilder();
        ASTContext& ast = cx.getASTContext();

        SVal returnValue = cx.getSVal(ret->getRetValue());
        SVal falseValue = builder.makeZeroVal(ast.BoolTy);
        SVal returnedFalse = builder.evalEQ(state, returnValue, falseValue);

        errs() << "Evaluating (" << returnValue << " == " << falseValue
            << ") -> " << returnedFalse << "\n";
    }
};

However when I run it on my sample code I get this output:

Evaluating (lazyCompoundVal{0x7f98f1871c70,Element{SymRegion{conj_$0{struct BoolConvertibleStruct *, LC1, S973, #1}},0 S64b,struct BoolConvertibleStruct}} == 0 U1b) -> Unknown

It seems to me that the result should be able to be easily modeled, but I'm not sure how to go about it. (I don't even see the "1" stored in the field showing up in the LazyCompoundVal.) Looking at the source code, it looks like I will have to somehow transform the LazyCompoundVal to something else because evalBinOp will always return Unknown if either side is a LazyCompoundVal. I have tried these things so far:

- Getting the CXXConversionDecl for BoolConvertibleStruct::operator bool() and creating a CXXMemberCallExpr, then calling cx.getSVal() on that; the result is Unknown
- Getting the FieldDecl for BoolConvertibleStruct::n and calling state->getLValue(field, returnValue); the resulting SVal crashes when I try to print it
- Using builder.evalCast() to cast returnValue to ast.BoolTy; the result of the cast is also Unknown
- Using state->isNull() to query the truth value directly; the result is underconstrained

Some suggestion about what to try next (or even "this definitely won't work") would be appreciated.

Regards,
--
Philip

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: [analyzer] Evaluating a call to operator bool()

Hubert Tong via cfe-dev
On 7/31/19 2:01 PM, via cfe-dev wrote:
Hi list,

I have the following code to analyze:

struct BoolConvertibleStruct {
    int n;
    BoolConvertibleStruct(int m) : n(m) {}
    operator bool() const { return n != 0; }
};

BoolConvertibleStruct StructFunc() {
    return 1;
}

I have reduced my problem to wanting to analyze StructFunc() to figure out the truth value of its return value. (The actual problem is more complicated and you might recognize that BoolConvertibleStruct is a stand-in for std::unique_ptr<T>, among other things :-P)

That's a very important detail. If it's a struct that you've implemented yourself, then all you need to do is extract the value from a field of the structure (it's not a problem when you know the name of the field).

However, if it's something in the C++ standard library and it's implemented differently depending on the particular implementation of the standard library that you may be using, then you can't access the field because you've no idea what meaning does every field carry, so you'll have to treat the structure as an opaque object and reason about its contents by modeling every method of the structure. I.e.:

1. Subscribe to the constructor of the structure and map (as in REGISTER_MAP_WITH_PROGRAMSTATE) the region of the structure to the value with which it was constructed.
2. Subscribe to the copy/move constructor of the structure and map the region into which it's copied/moved to the same value.
3. Subscribe to any method that mutates the value and update your maps.
4. Once you do all of this, you would be able to simply retrieve the value from your map when you need to model operator bool.

This approach is costly and annoying and easy to get wrong and i wish we had better tools for implementing it but for now it assumes a lot of boilerplate. If you want examples, see how the experimental IteratorChecker tries to model iterators (which is a harder problem).

Generally, i'll be pretty excited to accept patches that improve modeling of smart pointers in this manner. If that aligns with your interests, please extend our fairly minimal SmartPtrChecker and put your work to Phabricator (on an as early of a stage as possible) so that we could merge it!
I have the following sample checker:

class Analyzer : public Checker<check::EndFunction> {
 public:
    void checkEndFunction(const ReturnStmt* ret, CheckerContext& cx) const {

checkEndFunction isn't the right place for this sort of stuff. It's already too late to see what integer was stuffed into the returned BoolConvertibleStruct, but it's too early to actually model the effect of the call (what if it wasn't inlined in the first place?).

Regardless of what approach you take, you most likely want to stick to checkPostCall.

        const auto* func = cast<FunctionDecl>(cx.getStackFrame()->getDecl());
        if (func->getQualifiedNameAsString() != "StructFunc")
            return;

        ProgramStateRef state = cx.getState();
        SValBuilder& builder = cx.getSValBuilder();
        ASTContext& ast = cx.getASTContext();

        SVal returnValue = cx.getSVal(ret->getRetValue());
        SVal falseValue = builder.makeZeroVal(ast.BoolTy);
        SVal returnedFalse = builder.evalEQ(state, returnValue, falseValue);

        errs() << "Evaluating (" << returnValue << " == " << falseValue
            << ") -> " << returnedFalse << "\n";
    }
};

However when I run it on my sample code I get this output:

Evaluating (lazyCompoundVal{0x7f98f1871c70,Element{SymRegion{conj_$0{struct BoolConvertibleStruct *, LC1, S973, #1}},0 S64b,struct BoolConvertibleStruct}} == 0 U1b) -> Unknown

lazyCompoundVal is a snapshot of the structure as a whole. You can extract values of particular fields from it with the following procedure:

- Take the lazyCompoundVal's parent region (`LazyCompoundVal::getRegion()`, in your case it's `Element{SymRegion{conj_$0{struct BoolConvertibleStruct *, LC1, S973, #1}},0 S64b,struct BoolConvertibleStruct}`).
- Construct a FieldRegion as a sub-region of the parent region with the FieldDecl of the field (i.e., State->getLValue(fieldDecl, parentRegion)).
- Ask StoreManager to do a getBinding() for that region from the lazyCompoundVal's Store (`LazyCompoundVal::getStore()`, in your case it's `0x7f98f1871c70`).

See also my old workbook at https://github.com/haoNoQ/clang-analyzer-guide/releases/download/v0.1/clang-analyzer-guide-v0.1.pdf


It seems to me that the result should be able to be easily modeled, but I'm not sure how to go about it. (I don't even see the "1" stored in the field showing up in the LazyCompoundVal.)

I.e., in order to "see" "1" stored in the field, you need to dump the parent Store of the LazyCompoundVal. If you dump the Exploded Graph, you can easily search it by the Store pointer (it would, of course, change with every run).

Looking at the source code, it looks like I will have to somehow transform the LazyCompoundVal to something else because evalBinOp will always return Unknown if either side is a LazyCompoundVal. I have tried these things so far:

- Getting the CXXConversionDecl for BoolConvertibleStruct::operator bool() and creating a CXXMemberCallExpr, then calling cx.getSVal() on that; the result is Unknown
- Getting the FieldDecl for BoolConvertibleStruct::n and calling state->getLValue(field, returnValue); the resulting SVal crashes when I try to print it
- Using builder.evalCast() to cast returnValue to ast.BoolTy; the result of the cast is also Unknown
- Using state->isNull() to query the truth value directly; the result is underconstrained

Some suggestion about what to try next (or even "this definitely won't work") would be appreciated.

Regards,
--
Philip

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev


_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: [analyzer] Evaluating a call to operator bool()

Hubert Tong via cfe-dev
Hi,

Thanks! It took me a while since this original mailing list post, but I have now returned to this project and have made some progress.

On Thu, Aug 1, 2019 at 5:40 PM Artem Dergachev <[hidden email]> wrote:
On 7/31/19 2:01 PM, via cfe-dev wrote:
Hi list,

I have the following code to analyze:

struct BoolConvertibleStruct {
    int n;
    BoolConvertibleStruct(int m) : n(m) {}
    operator bool() const { return n != 0; }
};

BoolConvertibleStruct StructFunc() {
    return 1;
}

I have reduced my problem to wanting to analyze StructFunc() to figure out the truth value of its return value. (The actual problem is more complicated and you might recognize that BoolConvertibleStruct is a stand-in for std::unique_ptr<T>, among other things :-P)

That's a very important detail. If it's a struct that you've implemented yourself, then all you need to do is extract the value from a field of the structure (it's not a problem when you know the name of the field).

Indeed, I've gotten my contrived example to work (see below) but you're right it is quite different when dealing with std::unique_ptr.

However, if it's something in the C++ standard library and it's implemented differently depending on the particular implementation of the standard library that you may be using, then you can't access the field because you've no idea what meaning does every field carry, so you'll have to treat the structure as an opaque object and reason about its contents by modeling every method of the structure. I.e.:

1. Subscribe to the constructor of the structure and map (as in REGISTER_MAP_WITH_PROGRAMSTATE) the region of the structure to the value with which it was constructed.
2. Subscribe to the copy/move constructor of the structure and map the region into which it's copied/moved to the same value.
3. Subscribe to any method that mutates the value and update your maps.
4. Once you do all of this, you would be able to simply retrieve the value from your map when you need to model operator bool.

This approach is costly and annoying and easy to get wrong and i wish we had better tools for implementing it but for now it assumes a lot of boilerplate. If you want examples, see how the experimental IteratorChecker tries to model iterators (which is a harder problem).

I think I have got a skeleton of this approach almost working, but I seem to be stuck in some of the details. My program state map is from "const MemRegion*" to "SVal" (I don't think I need an additional map for symbols as in IteratorChecker?) However it seems that the MemRegion I get when subscribing to the constructor in PreCall or PostCall is different from the MemRegion I get when modeling std::unique_ptr::get() in EvalCall. I'm getting the MemRegion from the CallEvent like so (in the constructor I use CXXConstructorCall instead):

    const auto* instCall = cast<CXXInstanceCall>(&call);
    const MemRegion* thisRegion = instCall->getCXXThisVal().getAsRegion();
    if (thisRegion)
        thisRegion = thisRegion->getMostDerivedObjectRegion();

However, the returned region seems to be different in the constructor and in the get() method. For example I'm testing my code with a "struct MyStruct : std::unique_ptr<char>" and I'll get debug output such as:

    constructor: Storing 0 (Loc) into map with key SymRegion{conj_$5{struct MyStruct *, LC1, S3038538, #1}}
    get(): Retrieving key SymRegion{reg_$0<const struct MyStruct * this>}: not present

I did find https://reviews.llvm.org/D26762 linked from another mailing list post (http://lists.llvm.org/pipermail/cfe-dev/2017-June/054100.html), which seems like it might be related to where I'm stuck, but the code in that patch always seems to return None, maybe I'm not using it correctly.

Generally, i'll be pretty excited to accept patches that improve modeling of smart pointers in this manner. If that aligns with your interests, please extend our fairly minimal SmartPtrChecker and put your work to Phabricator (on an as early of a stage as possible) so that we could merge it!

I am trying to make the modeling work well enough for my own purpose first and then I will take a look at posting it to Phabricator. I will try to make it general enough to be able to submit it.
I have the following sample checker:

class Analyzer : public Checker<check::EndFunction> {
 public:
    void checkEndFunction(const ReturnStmt* ret, CheckerContext& cx) const {
        const auto* func = cast<FunctionDecl>(cx.getStackFrame()->getDecl());
        if (func->getQualifiedNameAsString() != "StructFunc")
            return;

        ProgramStateRef state = cx.getState();
        SValBuilder& builder = cx.getSValBuilder();
        ASTContext& ast = cx.getASTContext();

        SVal returnValue = cx.getSVal(ret->getRetValue());
        SVal falseValue = builder.makeZeroVal(ast.BoolTy);
        SVal returnedFalse = builder.evalEQ(state, returnValue, falseValue);

        errs() << "Evaluating (" << returnValue << " == " << falseValue
            << ") -> " << returnedFalse << "\n";
    }
};

However when I run it on my sample code I get this output:

Evaluating (lazyCompoundVal{0x7f98f1871c70,Element{SymRegion{conj_$0{struct BoolConvertibleStruct *, LC1, S973, #1}},0 S64b,struct BoolConvertibleStruct}} == 0 U1b) -> Unknown

lazyCompoundVal is a snapshot of the structure as a whole. You can extract values of particular fields from it with the following procedure:

- Take the lazyCompoundVal's parent region (`LazyCompoundVal::getRegion()`, in your case it's `Element{SymRegion{conj_$0{struct BoolConvertibleStruct *, LC1, S973, #1}},0 S64b,struct BoolConvertibleStruct}`).
- Construct a FieldRegion as a sub-region of the parent region with the FieldDecl of the field (i.e., State->getLValue(fieldDecl, parentRegion)).
- Ask StoreManager to do a getBinding() for that region from the lazyCompoundVal's Store (`LazyCompoundVal::getStore()`, in your case it's `0x7f98f1871c70`).

OK, I did get the contrived example to work that way. I guess the information I was missing was that I expected to be able to see any known bytes of the struct in the LazyCompoundVal, and that's not the case. In case any future readers want to know, the code I used is below. It turned out to be quite a lot more complicated than I expected and I am still not sure I understand why it works this way, but this seems to work.

        const Expr* returnExpr = ret->getRetValue();
        QualType returnType = returnExpr->getType();
        auto* klass = returnType.getTypePtr()->getAsCXXRecordDecl();
        const FieldDecl* nField = nullptr;
        for (const auto* field : klass->fields()) {
            if (field->getNameAsString() == "n")
                nField = field;
        }
        assert(nField);
        SVal returnValue = cx.getSVal(ret->getRetValue());
        Optional<nonloc::LazyCompoundVal> compoundValue = returnValue.getAs<nonloc::LazyCompoundVal>();
        assert(compoundValue);
        const TypedValueRegion* parentRegion = compoundValue->getRegion();
        SVal parentVal = state->getLValue(klass, parentRegion, /* isVirtual = */ false);
        SVal fieldVal = state->getLValue(nField, parentVal);
        Optional<Loc> fieldLoc = fieldVal.getAs<Loc>();
        assert(fieldLoc);
        StoreManager& storeManager = cx.getStoreManager();
        returnValue = storeManager.getBinding(compoundValue->getStore(), *fieldLoc);

Cheers,
--
Philip

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: [analyzer] Evaluating a call to operator bool()

Hubert Tong via cfe-dev
On 12/30/19 7:55 PM, [hidden email] wrote:
> However, the returned region seems to be different in the constructor
> and in the get() method. For example I'm testing my code with a
> "struct MyStruct : std::unique_ptr<char>" and I'll get debug output
> such as:
>
>     constructor: Storing 0 (Loc) into map with key
> SymRegion{conj_$5{struct MyStruct *, LC1, S3038538, #1}}
>     get(): Retrieving key SymRegion{reg_$0<const struct MyStruct *
> this>}: not present
That sounds strange because i think i fixed most of these problems
(https://www.youtube.com/watch?v=4n3l-ZcDJNY). Can you post the specific
code you're trying to analyze? Is your Clang fresh enough?
_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: [analyzer] Evaluating a call to operator bool()

Hubert Tong via cfe-dev
On Wed, Jan 1, 2020 at 11:57 AM Artem Dergachev <[hidden email]> wrote:
On 12/30/19 7:55 PM, [hidden email] wrote:
> However, the returned region seems to be different in the constructor
> and in the get() method. For example I'm testing my code with a
> "struct MyStruct : std::unique_ptr<char>" and I'll get debug output
> such as:
>
>     constructor: Storing 0 (Loc) into map with key
> SymRegion{conj_$5{struct MyStruct *, LC1, S3038538, #1}}
>     get(): Retrieving key SymRegion{reg_$0<const struct MyStruct *
> this>}: not present
That sounds strange because i think i fixed most of these problems
(https://www.youtube.com/watch?v=4n3l-ZcDJNY). Can you post the specific
code you're trying to analyze? Is your Clang fresh enough?

I'm using 9.0.0, would this be something that I need to build the master branch for?

The test code I'm trying to analyze is this:

struct Context;
char* StringFunc(Context* cx);

struct MyStruct : public std::unique_ptr<char> {
    MyStruct(char* p) : MyStruct::unique_ptr(p) {}
    operator bool() const { return !!get(); }
};

MyStruct OkayBoolConvertibleReturn(Context* cx) {
    char* ptr = StringFunc(cx);
    if (ptr)
        return ptr;
    return nullptr;
}

Cheers,
--
Philip

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: [analyzer] Evaluating a call to operator bool()

Hubert Tong via cfe-dev
Operator bool() is never invoked in your code. The body of the operator
is analyzed separately, outside of any known context (call site). For
that reason you get a "symbolic region" for it, which is a notation for
the memory region around a pointer that isn't known to point into any
specific memory location on the current execution path (in this case,
it's the pointer 'this' during the unknown invocation of operator
bool()). A symbolic region is always an alias for a particular
"concrete" region, it's simply not known *which* one; it may or may not
be the struct you've constructed in your other function.

Studying the static analyzer by printing values to standard output may
get very confusing because the analyzer doesn't explore the program in
any particular linear order. Analysis is much better represented as a
graph which can be easily dumped
(https://www.youtube.com/watch?v=g0Mqx1niUi0). If you want to debug your
checker this way, you should implement the "printState()" method in the
checker, so that to see the extra information from it in the graph.

On 1/11/20 9:05 AM, [hidden email] wrote:

> On Wed, Jan 1, 2020 at 11:57 AM Artem Dergachev <[hidden email]
> <mailto:[hidden email]>> wrote:
>
>     On 12/30/19 7:55 PM, [hidden email]
>     <mailto:[hidden email]> wrote:
>     > However, the returned region seems to be different in the
>     constructor
>     > and in the get() method. For example I'm testing my code with a
>     > "struct MyStruct : std::unique_ptr<char>" and I'll get debug output
>     > such as:
>     >
>     >     constructor: Storing 0 (Loc) into map with key
>     > SymRegion{conj_$5{struct MyStruct *, LC1, S3038538, #1}}
>     >     get(): Retrieving key SymRegion{reg_$0<const struct MyStruct *
>     > this>}: not present
>     That sounds strange because i think i fixed most of these problems
>     (https://www.youtube.com/watch?v=4n3l-ZcDJNY). Can you post the
>     specific
>     code you're trying to analyze? Is your Clang fresh enough?
>
>
> I'm using 9.0.0, would this be something that I need to build the
> master branch for?
>
> The test code I'm trying to analyze is this:
>
> struct Context;
> char* StringFunc(Context* cx);
>
> struct MyStruct : public std::unique_ptr<char> {
>     MyStruct(char* p) : MyStruct::unique_ptr(p) {}
>     operator bool() const { return !!get(); }
> };
>
> MyStruct OkayBoolConvertibleReturn(Context* cx) {
>     char* ptr = StringFunc(cx);
>     if (ptr)
>         return ptr;
>     return nullptr;
> }
>
> Cheers,
> --
> Philip

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: [analyzer] Evaluating a call to operator bool()

Hubert Tong via cfe-dev
On Fri, Jan 10, 2020 at 10:47 PM Artem Dergachev <[hidden email]> wrote:
Operator bool() is never invoked in your code. The body of the operator
is analyzed separately, outside of any known context (call site). For
that reason you get a "symbolic region" for it, which is a notation for
the memory region around a pointer that isn't known to point into any
specific memory location on the current execution path (in this case,
it's the pointer 'this' during the unknown invocation of operator
bool()). A symbolic region is always an alias for a particular
"concrete" region, it's simply not known *which* one; it may or may not
be the struct you've constructed in your other function.

OK, got it, I think I understand now that looking at operator bool() is a red herring in my case. What I'm trying to do is to see whether, at the end of any path through the function, the pointer in MyStruct is known to be null or known to be non-null. Not sure why I had convinced myself that operator bool() would be called in that situation ... :-) So maybe I should actually be able to achieve this by just consulting the program state map at the exit point of the function.

Studying the static analyzer by printing values to standard output may
get very confusing because the analyzer doesn't explore the program in
any particular linear order. Analysis is much better represented as a
graph which can be easily dumped
(https://www.youtube.com/watch?v=g0Mqx1niUi0). If you want to debug your
checker this way, you should implement the "printState()" method in the
checker, so that to see the extra information from it in the graph.

Can I do this without compiling my own clang and llvm? I have tried to figure out how to dump the graph before, but I thought I ran into the problem of having to have a debug-enabled copy of clang.

By the way, these talk videos you've sent have really been quite useful, and I think they'd be prime candidates for turning into documentation or blog posts! I haven't yet found time to watch them all the way through, but documentation would be searchable and browseable :-)
 
On 1/11/20 9:05 AM, [hidden email] wrote:
> On Wed, Jan 1, 2020 at 11:57 AM Artem Dergachev <[hidden email]
> <mailto:[hidden email]>> wrote:
>
>     On 12/30/19 7:55 PM, [hidden email]
>     <mailto:[hidden email]> wrote:
>     > However, the returned region seems to be different in the
>     constructor
>     > and in the get() method. For example I'm testing my code with a
>     > "struct MyStruct : std::unique_ptr<char>" and I'll get debug output
>     > such as:
>     >
>     >     constructor: Storing 0 (Loc) into map with key
>     > SymRegion{conj_$5{struct MyStruct *, LC1, S3038538, #1}}
>     >     get(): Retrieving key SymRegion{reg_$0<const struct MyStruct *
>     > this>}: not present
>     That sounds strange because i think i fixed most of these problems
>     (https://www.youtube.com/watch?v=4n3l-ZcDJNY). Can you post the
>     specific
>     code you're trying to analyze? Is your Clang fresh enough?
>
>
> I'm using 9.0.0, would this be something that I need to build the
> master branch for?
>
> The test code I'm trying to analyze is this:
>
> struct Context;
> char* StringFunc(Context* cx);
>
> struct MyStruct : public std::unique_ptr<char> {
>     MyStruct(char* p) : MyStruct::unique_ptr(p) {}
>     operator bool() const { return !!get(); }
> };
>
> MyStruct OkayBoolConvertibleReturn(Context* cx) {
>     char* ptr = StringFunc(cx);
>     if (ptr)
>         return ptr;
>     return nullptr;
> }
>
> Cheers,
> --
> Philip


Cheers,
--
Philip

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: [analyzer] Evaluating a call to operator bool()

Hubert Tong via cfe-dev

On 1/21/20 8:22 AM, [hidden email] wrote:
> Can I do this without compiling my own clang and llvm? I have tried to
> figure out how to dump the graph before, but I thought I ran into the
> problem of having to have a debug-enabled copy of clang.

Yes, i think you have to compile your clang for this (with assertions,
but not necessarily unoptimized). You need to compile it in order to
build your checker as well (or are you using the plugin interface? it's
very unstable!).

> By the way, these talk videos you've sent have really been quite
> useful, and I think they'd be prime candidates for turning into
> documentation or blog posts! I haven't yet found time to watch them
> all the way through, but documentation would be searchable and
> browseable :-)

Well, yeah. Also we have a collection of useful links in
https://clang-analyzer.llvm.org/checker_dev_manual.html

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev