Language

The Free and Open Productivity Suite
Released: Apache OpenOffice 4.1.15
Stack Traces for UNO Exceptions in C++ UNO Environments

Stack Traces for UNO Exceptions in C++ UNO Environments

OpenOffice.org

This document describes a solution for automatically provided stack traces for arbitrary UNO exceptions in C++ UNO environments. These stack traces can be used to determine the code that actually threw the exception.

Motivation

When debugging Offices exiting due to unexpected exceptions it is often difficult to find the particular source line that has thrown the exception. A solution for this problem could be to provide a way to obtain a stack trace for each (UNO) exception thrown. Ideally, the stack trace points exactly to the source line that has thrown the exception.

This feature is certainly a good debugging tool, not only for the scenario described above.

Prerequisites

According to the Platform Technology Team it is possible to provide a helper function that generates a stack trace for the current thread for all of our supported platforms. The actual content of the generated stack trace depends on the compiler and linker options used to produce the binaries. It may contain very precise information like function names including line numbers or just function addresses.

Additionally, performance might become a problem. Generating a stack trace can take up to several seconds, depending on the concrete platform and again, compiler and linker settings used to build the binaries. Further investigation and measurements must be done by the Platform Technology Team.

Requirements

  1. It shall be possible to automatically create stack traces for every UNO exception created and thrown.
  2. Solution should be compatible with UNO3.

Possible Solutions

Cppumaker generates the C++ class com::sun::star::uno::Exception for exception com.sun.star.uno.Exception declared in IDL. Generally, it is a good approach to add a new member to class css::uno::Exception for holding the stack traces. This is fine, but we have the requirement to stay compatible with UNO3. Therefore we are not allowed to add another member to css::uno::Exception.

Luckily, css::uno::Exception already has a member rtl::OUString Message, which is supposed to transport user supplied messages. The idea is to reuse this member for stack traces. This solution is compatible with UNO3. On the other hand we have to ensure that user messages will not get lost. Therefore the stack traces will simply be appended to the user message.

Note: 'Message' is a public member of class com::sun::star::uno::Exception. Thus its content can be overridden by everyone at any time.

To summarize, introducing a new member for holding stack traces is a clean solution and should be done for a successor of of UNO3 which can become incompatible. For a quick solution that is compatible with UNO3 we can reuse the existing 'Message' member.

Details

IDL Type Changes

For UNO4, a new member StackTrace must be added to com.sun.star.uno.Exception IDL specification. Defining the member in IDL rather as an implementation detail of the UNO C++ language binding has the benefit that the stack traces can be inspected even over (remote) UNO bridges.

For the UNO3 solution, there are no IDL type changes necessary.

When to generate stack traces?

cppumaker must generate special code for class com::sun::star::uno::Exception. This includes generation of the code that automatically generates and fills in the stack traces.

Due to the possible performance problems with stack trace generation there should be a flexible mechanism for configuring this feature. There can be different strategies:

Control Mechanism Precondition
Stack trace generation always active - possible to generate meaningful traces for both debug and product builds
- no performance problems
#ifdef DEBUG around stack trace generating code - no meaningful stack traces for product build
- no performance problems
#ifdef <PLATFORM> around stack trace generating code - performance problems on some platforms only
Control stack trace generation via environment variable – default (var not present): ON - no performance problems
Control stack trace generation via environment variable – default (var not present): default: OFF - performance problems

The most flexible way is certainly to provide an environment variable. The default behavior depends on the actually performance problems. Further investigations are necessary for defining this.

Where to generate stack traces?

Code for generating a stack trace must be added to every constructor of class com::sun::star::uno::Exception, including the copy constructor.

The original idea that it is sufficient to add stack generating code to the copy constructor only turned out to be false. The assumption made was that the copy constructor will always be called when throwing an exception before leaving the current scope. But the compiler is allowed to optimize here and to omit the copy construction. A test on Windows using MSVC603 (compiler version 12.00.8168) confirmed this. The following code does not call the copy constructor of class MyException.

class MyException
{
    ...
};

{
    ...

    // MyException copy ctor not called
    try
    {
        throw MyException();
    }
    catch ( MyException const & )
    {
    }
}

In an ideal solution one would expect exactly one stack trace per thrown exception. The stack trace should point to the throwing function (or even better to the line number of the throwing statement). This goal is hardly to achieve without multiplying efforts. Reasons for this:

  1. At a given time, there can exist multiple valid stack traces for one exception object. That means, the exception is thrown more than once. Examples:

    1. One exception object may be thrown by different threads at the same time. Each throw has its own stack trace.
    2. One exception may be 'active' multiple times, even in one thread. Note that even in this case the MSVC603 compiler does not call the MyException copy constructor. Therefore there will be no stack trace generated for the rethrow in the outer catch handler of main.
class MyException
{
    ...
};

class MySpecializedException1 : public MyException
{
    ...
};

class MySpecializedException2 : public MyException
{
    ...
};

{    
    ...

    try
    {
        throw MySpecializedException1();
    }
    catch ( MyException const & )
    {	
        try
        {
	  // rethrow to handle more detailed
            throw;
        }
        catch ( MySpecializedException1 const & )
        {	
        }
        catch ( MySpecializedException2 const & )
        {
        }
    }
}
  1. Due to the fact that between construction (where the stack trace is generated) and throwing of an object can be arbitrary code an exception object may contain more or less unusable stack traces at the time it is caught.

    1. If the object is created and thrown in the same function the stack is usable. The only thing that might be wrong is the source line number (if available at all).
    2. If the object was passed to other functions (via reference) after construction and thrown be one of these functions, the stack trace contained in the exception object does not even point to the throwing function. Happily, in this scenario the compiler will (must?) call the copy constructor of the object to throw before throwing it and a usable stack trace will be appended.

Note: Catching an exception by value instead of catching it by reference will lead in an additional call to the copy constructor of the thrown object and therefore add another useless stack trace. Anyway, catching exceptions via value is generally bad practice, regardless of this proposal.

Because of the fact

it is important not to omit any existing stack traces when adding a new trace to the exception.

Content of a Stack Trace Entry

As there might be more than one stack trace for an exception there should be added a header for each stack trace appended to the exception. This header should contain a time stamp and a thread id.

Summary

The proposal provides a solution for supplying UNO exceptions with stack traces automatically. The solution is compatible with UNO3. There is a slightly cleaner solution that is incompatible but should be chosen for UNO4. It should be easy to migrate from the UNO3 to the UNO4 solution.

Although there are even cases where a stack trace is not created at all when an exception is thrown, the proposed solution covers mainly all use cases that occur in practice. All the failing or problematic cases occur very seldom in real life. The most common way is to construct and throw an exception in the same line of code, for which creating a useful stack trace works fine.

Next Steps

Author: Kai Sommerfeld (Last modification $Date: 2004/12/08 11:45:30 $). Copyright 2002 OpenOffice.org Foundation. All Rights Reserved.

Apache Software Foundation

Copyright & License | Privacy | Contact Us | Donate | Thanks

Apache, OpenOffice, OpenOffice.org and the seagull logo are registered trademarks of The Apache Software Foundation. The Apache feather logo is a trademark of The Apache Software Foundation. Other names appearing on the site may be trademarks of their respective owners.