Skip to content

Entries from July 2011.

Collecting crash reports over UDP using netcat

Collecting runtime errors (crashes, failed assertions, ...) is very important part of software quality efforts. If you know crash details from your testing team you can handle them even before a tester writes first line of error report (!). That improves development speed.

Probably the fastest method how to create KISS (Keep It Simple Stupid) central crash report repository is to use:

  • netcat - command line UDP server
  • crontab - for daily logs rotation

Let's see the crontab entry:

0 0 * * *    killall -9 -q netcat; while true; do echo "A"; sleep 0.1; done | netcat -v -k -u -l 4000 \
    >> crash/crash-`date +%Y%m%d`.log 2>&1 &

/dev/zero as input needed for some reason (otherwise process will exit after first crash report). "date"/"kill" allows to split crash reports per day. "-l 4000" is the port definition, "-u" tells netcat to use UDP instead of TCP (the default).

Crash handlers inside tested programs must open UDP connection to above server and send textual representation of stacktrace (should be available in rutime via reflection).

And sample result from log file (C++, but one may consider Java/Python as implementation language):

Connection from 192.168.4.168 port 4000 [udp/*] accepted
stack trace (libstacktrace init) for process /bin/busybox (PID:1342, VER=master:0f9cc45:vip1963):
  (PID:1342) /usr/local/lib/libbacktrace.so : install_handler()+0x198
  (PID:1342) /usr/local/lib/libbacktrace.so [0x29589e78]: ??
  (PID:1342) /usr/local/lib/libbacktrace.so [0x2958db6a]: ??
stack trace END (PID:1342)

Fighting with NullPointerException in C++, the static way

Dereferencing NULL pointer is a very common programming error in almost any programming language that supports pointers. It cannot be caught at build time in general, so we can carefully check every pointer before dereference and handle errant cases in runtime (warning in log?).

But above method is a runtime method. If you don't have proper code coverage by tests it might not detect errant cases. I believe the answer for this issue lies in static methods (performed at build/before runtime phase). Good example of such approach is LCLint:

char firstChar2 (/*@null@*/ char *s)
{
   if (isNull (s)) return '\0';
   return *s;
}

As you can see LCLint uses annotations to mark parameter that might have NULL value and thus can detect dereferencing NULL. But LCList is only designed for C language and cannot check C++ (C++ is more complicated for parsing).

QT framework introduces smart pointer with reference counter-based garbage collection: QSharedPointer. This smart pointer allows for NULL value, can we make extension that will not accept NULL? Let's see:

template  class QNullableSharedPointer : public QSharedPointer {
public:
    QNullableSharedPointer();
    QNullableSharedPointer(QSharedPointer sp) { QSharedPointer::QSharedPointer(sp); }
    QNullableSharedPointer(T* ptr);
    QNotNullSharedPointer castToNotNull() {
        return QNotNullSharedPointer(*this);
    }
};

template  class QNotNullSharedPointer : public QNullableSharedPointer {
public:
    QNotNullSharedPointer(T* ptr);
protected:
    QNotNullSharedPointer();
    QNotNullSharedPointer(QNullableSharedPointer sp) { QNullableSharedPointer::QNullableSharedPointer(sp); }
    // T* operator-> () const;
    friend class QNullableSharedPointer;
};

As you can see I hide some methods in QSharedPointer subclass called QNullableSharedPointer. If you declare: "QNullableSharedPointer x" you cannot dereference "x->property" because x might hold a NULL value. In order to make dereference you have to cast to QNotNullSharedPointer: "x.castNotNull()".

Of course you can ask: "Hey, one can cast from nullable to null without any test for isNull()". That's correct. But casting can be easily scaned in source code and you can review those places carefully (or by some automated tool). It's only check that is left to human, rest will be handled by compiler (proper variant of pointer used).

As you probably noticed QNotNullSharedPointer might be called QSharedReferece instead to resemble fact that referecens in C++ should be always initialised. Yes, it's kind of "reference".

I haven't located any implementation of that pattern instead of this post ("Why no non-null smart pointers?" in context of Boost library). Do you know similar build-time techniques that can lower probability of programming error in C++ (thus increase of quality)?

GIT merge status

If you are merging/cherry-picking changes frequently between GIT branches it's very useful to know exactly what changes were already merged, what changes are waiting for merge and for wchich change there will be a conflict during merge.

This information should be available from "git log", but unfortunately I dif not get good results (even with --cherry-pick). Then some other solution must be prepared.

I decided to create a small script that will perform series of cherry-picks and prepare a report that shows integration status. Usage is pretty simple: $ git-merge-status SHA1..SHA2

Note that due to internal GIT commit storage source branch selection is not necessary because pair of commit IDs will point exactly the codebase. Sample run:

$ git-merge-status 32e5886..568f4c0
+ 32e5886 Dariusz Cieslak Version upgraded to 0.17
. e54772d T* S* (#2101) Preload two pages when switching pages
. ea76457 R* S* Implement trickplay icon (#2050)
. bb18d64 R* S* Force trickplay icon update (#2050)
. f45efab R* S* Fix zapbanner hours played (#2159)
. 73411c4 R* S* Inherit parent's VOD skin
. 7446a00 D* S* red light on front display while recording (#1502) (cherry picked from commit 6e049cc2f81947621b17105869e312b559164ce9)
. 983d4cc D* S* pvrservice: fromdos (only formatting!) (#2183) (cherry picked from commit db6de56171a90a9bd8eed681f4db7177819611a3)
. 37ed65f A* R. C* (#1711) TSTVEnableDelay key in system.properties (cherry picked from commit c094431ac17fc910a55163b8680c8fb81913dad5)
. 139f4a0 R* S* Display all movie icons in zapbanner
. 5230758 R* S* Fix VOD movie length display (#2139)
C b80a5fb Dariusz Cieslak Appman scripts placed in mw/config/etc/... (#1629)
. 1c8ff68 D* M* (#2186) Fixed qt signals in PLTVHelper
(...)

already merged (.): 49
to be merged (+): 13
conflict during merge(C): 26

Script body:

#!/bin/sh

git log --pretty=format:"%h %cn %s" --reverse $* | awk '
BEGIN {
    system("git reset --hard >/dev/null 2>&1")

    CMD="git log --decorate --pretty=oneline --abbrev-commit"
    while (CMD | getline result > 0) {
        history[substr(result, 0, 7)] = 1
        if (result ~ /cherry picked from commit/) {
            CL2 = $0
            sub(/.*commit /, "", CL2)
            CL2 = substr(CL2, 0, 7)
            if (CL2 in history) {
                revhistory[CL2] = 1
            }
        }
    }
    close(CMD)
}
{
    CID=$0
    gsub(/"/, " ", CID)

    CL=$3

    if (CL in revhistory) {
        # already merged in opposite direction
        print ". " CID
        PREVIOUSLY_MERGED ++
        next
    }

    if (/cherry picked from commit/) {
        CL2 = $0
        sub(/.*commit /, "", CL2)
        CL2 = substr(CL2, 0, 7)
        if (CL2 in history) {
            print ". " CID
            PREVIOUSLY_MERGED ++
            next
        }
    }

    CMD="git cherry-pick -x " $1 " 2>&1"
    buf = ""
    while (CMD | getline result > 0) {
        buf = buf result
    }
    close(CMD)

    if (buf ~ /files changed/) {
        # Just merged
        print "+ " CID
        JUST_MERGED ++
    }
    else if (buf ~ /Automatic cherry-pick failed/) {
        # Conflict, skip
        print "C " CID
        system("git reset --hard >/dev/null 2>&1")
        CONFLICT ++
    }
    else {
        # Already merged
        print ". " CID
        PREVIOUSLY_MERGED ++
    }

}
END {
    print ""
    print "already merged (.): " PREVIOUSLY_MERGED
    print "to be merged (+): " JUST_MERGED
    print "conflict during merge(C): " CONFLICT
}
'