Beruflich Dokumente
Kultur Dokumente
Coverity Prevent
on Symbian OS
Coverity Prevent on Symbian OS
part of the SDN++ series
1st edition, 10/08
Published by:
Symbian Software Limited
2-6 Boundary Row
Southwark
London SE1 8HP
UK
www.symbian.com
Contributors:
Ben Chelf, CTO, Coverity
Andy Chou, Chief Architect, Coverity
Philippe Gabriel, Symbian Software Ltd.
Sumant Kowshik, Senior Software Engineer, Coverity
Jim Shissler, Director, Coverity
Managing Editor:
Ashlee Godwin
Reviewers:
Philippe Gabriel, Symbian Software
Cathy Hoffman, Director, Business Development, Coverity
Chris Notton, Symbian Software
Jo Stichbury, Symbian Software
Mats Trovik, Symbian Software
Additional Acknowledgements:
Azita Esmaili, Symbian Software
Andy ‘Chief’ Harker, Symbian Software
Lisa Voisin, Copy Editor
Contents
Introduction .................................................................................................................................1
The value of early defect detection ............................................................................................1
Accuracy of results......................................................................................................................2
Multi-core hardware and proactive concurrency defect detection .............................................3
Coverity Prevent..........................................................................................................................3
Symbian-specific defect detection ..............................................................................................5
Implementing static analysis in Symbian environments............................................................5
Installing Coverity Prevent in Symbian environments................................................................6
Coding errors and their potential consequences on Symbian OS.............................................8
Checker: FORWARD_NULL...................................................................................................8
Checker: FORWARD_NULL (special case)............................................................................9
Checker: SYMBIAN.CLEANUP_STACK .................................................................................10
Checker: NULL_RETURNS ..................................................................................................15
Checker: EVALUATION_ORDER...........................................................................................16
Checker: DEADCODE..........................................................................................................17
Checker: REVERSE_NEGATIVE ...........................................................................................18
Checker: USE_AFTER_FREE ...............................................................................................19
Checker: OVERRUN_STATIC ...............................................................................................19
Conclusion.................................................................................................................................20
Checklist for selecting a static analysis tool ............................................................................21
Resources ..................................................................................................................................22
1
Introduction
The market for data-enabled smartphones continues to redefine the mobile device industry.
Consumers who have become accustomed to relying on PDAs and laptops now expect that
same functionality from their smartphone.
As developers, we are challenged to push the boundaries of mobile computing, and create
ever more sophisticated applications to meet the growing demands of next-generation
smartphone users. We are tasked with delivering new, easy-to-use applications that provide
increasingly sophisticated functionality. However, the path to greater end-user simplicity is
often through greater underlying complexity at the application code level. To increase the
challenge, the emergence of multi-core hardware introduces another layer of complexity for
us, since it increases the likelihood of critical software defects. Crashes, reboots and data loss
are unacceptable to customers and vendors alike, so ensuring the reliability, security and
overall integrity of our code is mandatory to ensure the success of our applications.
To ensure mobile application stability, we must find and fix critical code defects accurately
and efficiently. The traditional methods used for defect detection, QA testing and manual code
review, no longer guarantee quality or prevent field defects in today’s ever expanding, more
complex code bases. Because these testing techniques are commonly applied during the
software’s runtime execution, the number of paths through an application is severely limited.
Additionally, most testing techniques are employed late in the development cycle, when
defects are more costly and time consuming for development teams to eliminate.
To improve code quality, we recommend source code analysis to find and fix defects. An
automated tool is the best way to ensure that every possible path through the software
system is explored. Internally, Symbian uses the Coverity Prevent static analysis tool to
analyze Symbian OS, and this booklet examines the tool in detail. We explain how you can
use static source code analysis to automatically detect defects at the earliest stage in the
development cycle, to ensure the optimal quality and security of your Symbian C++
application.
In addition, this booklet presents specific examples of defects commonly found in Symbian
applications and explains how a static analysis tool can help you prevent these defects from
making it into your final release.
Simply deploying static analysis will not help an organization increase the integrity of the
code it delivers. The tool must meet the developers’ needs by delivering an array of features
that generate high quality results. For Symbian C++ developers, the high-level features
required to ensure a successful experience with static analysis are:
• accuracy of results
• multi-core support
• Symbian-specific checking.
Accuracy of results
Many static analysis tools rely only on simple pattern matching to identify coding violations
and suspicious code constructs. While this approach is scalable for large code bases, its
analysis is inherently shallow because it fails to provide a complete understanding of the
code’s semantics. These pattern-matching tools rarely identify truly serious, crash-causing
defects. They also frequently suffer from high false-positive rates, thereby resulting in time
consuming manual defect reviews and reducing the tool’s overall value. False-positive rates
are an inherent issue with any static analysis tool due to the technology’s underlying
methodology. However, false-positive rates do vary significantly between tools. To ensure
developer adoption, a static analysis tool should deliver accurate and relevant results,
reflected in a documented false-positive rate of 20% or less.
The accuracy of a static analysis tool is related to the software system that it analyzes. The
most accurate solutions construct a complete representation of the software system. A tool
should be able to:
• Capture all operations the build system performs to understand exactly how each file is
compiled and how they are all linked.
• Create an authentic compilation of every source file in the build system to make sure the
analyzed version of that file is semantically equivalent to the output that was generated by
the compiler.
• Deliver a high-fidelity representation of the entire software system at the file, function,
statement and even expression level.
With a complete, accurate representation of the software system, an effective static analysis
tool performs deep inter-procedural analysis to uncover the defects that matter most to
developers. The term ‘inter-procedural analysis’ refers to the analysis of a whole program that
crosses the boundaries of methods or functions, thus taking into account the effects of a
function call or method invocation when analyzing the caller.
The most sophisticated static analysis can perform inter-procedural analysis with an arbitrary
call chain depth, while keeping track of context-sensitive information, such as parameter and
return values and their influence on the program’s potential flow. With this type of inter-
procedural analysis, static analysis can deliver highly accurate results. While some static
analysis solutions focus on programming style and syntax-based checks, the depth of analysis
for applications in a Symbian environment requires 100% coverage of all values and data
paths via inter-procedural analysis – not partial path coverage that can leave dangerous blind
spots in unexplored code.
3
Static analysis tools that deliver 100% path coverage are invaluable to developers creating
multi-threaded applications. They enable us to detect crash-causing concurrency defects that
are nearly impossible to find with other techniques. Additionally, because mainstream
adoption of multi-core hardware is a relatively recent phenomenon, many of us are learning
to create multi-threaded applications on the job. For those who are unfamiliar with complex
interleavings, static detection of concurrency defects can immediately improve the quality of
our multi-threaded code. Common concurrency defects include:
• Race Conditions – Multiple threads access the same shared data without the appropriate
locks to protect access points. When this defect occurs, one thread may inadvertently
overwrite data used by another thread, leading to both loss of information and data
corruption.
• Deadlock – Two or more threads wait for a lock in a circular chain such that the locks can
never be acquired. When this defect occurs, the entire software system may halt, as none
of the threads can either proceed along their current execution paths or exit.
• Thread Block – A thread calls a long-running operation while holding a lock, thereby
preventing the progress of other threads. When this defect occurs, application performance
can drop dramatically due to a single bottleneck for all threads.
Coverity Prevent
To analyze and ensure the integrity of Symbian OS code, Symbian uses Coverity Prevent to
perform static analysis. Symbian selected Prevent because it delivers a complete view of every
operation by mapping out the full build system with its Software DNA Map analysis system. The
tool performs deep inter-procedural analysis to achieve 100% path coverage. In addition, it uses
Boolean Satisfiability (SAT) to achieve 100% value coverage. Boolean Satisfiability translates the
code into questions based on Boolean values and then applies existing SAT solvers to test for:
SAT significantly improves testing accuracy by detecting critical arithmetic and logic problems,
like integer overflows and buffer overflow.
Out of the box, Coverity Prevent features the lowest documented false-positive rate in static
code analysis — an average of below 15% — detecting only the critical bugs that matter the
most to developers.
4
Figure 1 summarizes the process of defect detection with Coverity Prevent. Further information is
available from www.coverity.com/html/prevent-for-c-c++.html.
Coverity Prevent detects a number of critical quality and security defects, including:
Performance degradation:
• memory leaks
• file handle leaks
• custom memory and network resource leaks
• database connection leaks.
Crash-causing defects:
• NULL pointer deference
• use after free
• double free
• improper memory allocations
• mismatched array new/delete.
Defect implications:
• total system compromise
• denial of service attacks
• privilege escalation
• leaking confidential data
• data loss
• arbitrary code execution.
Concurrency:
• deadlocks
• race conditions
• blocking call misuse.
The most useful static tools offer the flexibility to analyze code on desktops or central build
systems and review defects anywhere through the developer’s web browser of choice. For large-
scale Symbian applications, it is also essential that the solution can scale to analyze large,
complex code bases quickly.
A static analysis tool should provide a customizable workflow through which developers can
assign, distribute and then collaborate to resolve defects. To ensure that defects can be
identified accurately and efficiently, the following capabilities are critical:
6
• Automatic Assignment of Defects – Tools that only find defects create significant
management overhead for administrators and end users. A static analysis tool should also
pinpoint which individual developer is responsible for any defect for a given code base.
Without this facility, defect assignment is time consuming and fails to provide managers
with a high-level view of the quantity of defects introduced by individual developers.
• Developer prioritization of defects – Static analysis tools should provide customizable
severity settings to identify real defects, coding violations, false-positives, real but
unimportant errors and so on. To complement existing workflow, developers should be
able to assign actions to be taken as a result of any defect discovered in the code.
• Persistent tracking of defects – One common deployment pitfall for static analysis tools is
tracking developer analysis of potential defects over time. As code churns, developers
require a static tool to track which defects are new and which have already been
diagnosed. The persistence of status across multiple analysis runs avoids the time-
consuming task of sifting through familiar results to hunt for new defects.
Lastly, to achieve greater accuracy, tuning the analysis after installation often further lowers
false-positive rates. Tuning typically involves modifying either the number of checkers
deployed or the settings specific to individual checkers. This allows static analysis to become
more precise over time as development teams learn more about how their tool comprehends
the code they create.
The covbuild command can automatically detect invocations of the compiler. This method
requires no changes to the build system itself. Instead, it relies on ‘wrapping’ the build
system so that Coverity Prevent can piggyback on the compiler invocations. The regular build
system is invoked by the covbuild command, which sets up the operating environment such
that all calls to exec(unix) and CreateProcess(win32) made by the build process and
its descendant processes are intercepted by the Coverity Prevent capture stub library.
After executing the covbuild command, completing the analysis of your code requires only a
few additional steps:
Checker: FORWARD_NULL
Dereferencing a NULL pointer causes a program to crash. The FORWARD_NULL checker finds
instances where a pointer is not properly checked against NULL before it is dereferenced.
Defects typically arise when the code checks for NULL but then does not properly handle the
condition, or does not check for NULL in the code path at all.
9
...
Event var_compare_op: Added "aWsAnim" due to comparison "aWsAnim"
At conditional (0): "aWsAnim" taking false path
79 if(aWsAnim && aWsAnim->iWindow)
80 {
81 aWsAnim->iWindow->Screen()->LayerROptimizer().RSched(*(aWsAnim->
iWindow));
82 }
83
Event var_deref_op: Variable "aWsAnim" tracked as NULL was
dereferenced.
84 TInt handle=aWsAnim->iAnimDll->AnimObjectHandle(aWsAnim);
85 if (handle<0)
86 delete aWsAnim;
87 else
88 aWsAnim->iAnimDll->Remove(handle);
...
Explanation of defect: In the example above, aWsAnim is compared to NULL (line 79), thereby
confirming the programmer’s belief that it could be invalid. However, later, in line 84,
iAnimDll is accessed - by dereferencing the pointer - even though it could potentially be
NULL. If aWsAnim is NULL, a KERN-EXEC 3 panic occurs (and if the code is running in a
system-critical executable, the device reboots).
...
509 CResolvedClient2* resolvedClient2 = NULL;
Event assign_zero: Variable "resolvedClient" assigned value 0.
510 CResolvedClient* resolvedClient = NULL;
511
At conditional (0): "aWorkerAo->Client()->PluginType() == 1" taking
false path
512 if ( aWorkerAo.Client().PluginType() == TClient::EFirstGeneration )
513 {
514 resolvedClient =
515 iCRServer.LoadPlugin1LC( aWorkerAo.Client().Uid(),
516 aWorkerAo.ResolverUid() );
517 }
518 else
519 {
520 resolvedClient2 =
521 iCRServer.LoadPlugin2LC( aWorkerAo.Client().ImplementationUid() );
522 }
523
524 TUid channelUid;
Event var_deref_model: Variable "resolvedClient" tracked as NULL was
passed to function "CRServerSession::ChannelUidL(const TClient &, TUid
10
Explanation of defect: Along the path represented in the example above, resolvedClient
is initialized to NULL and never updated.
In the Defect Manager, clicking on the ‘details’ hyperlink takes you to the
CRServerSession::ChannelUidL() method that dereferences the pointer, as shown below.
As previously described, dereferencing a NULL or invalid pointer results in a KERN-EXEC 3 panic.
...
568 void CRServerSession::ChannelUidL(const TClient& aClient,
569 TUid& aChannelUid,
570 CRequest* aRequest,
571 CResolvedClient* aResolvedClient))
...
583 {
Event deref_parm_in_call: Dereferenced parameter "aResolvedClient"
in function "CResolvedClient::ChannelL(RStringF, const TDesC8 &,
const RPointerArray<CHeaderBase> &, const TDesC8 &, const
CContentTypeHeader *)"
584 aChannelUid = aResolvedClient->ChannelL(aRequest->Method(), *uri,
aRequest->AllHeadersL(),
aRequest->Content(),
contentTypeHeader);
...
Checker: SYMBIAN.CLEANUP_STACK
Symbian OS uses leaves (which are like lightweight exceptions) for reporting errors. To handle
memory in the event of a leave, the cleanup stack is used to free outstanding memory and
avoid resource leaks.
The SYMBIAN.CLEANUP_STACK checker verifies that allocated memory always has exactly one
owner responsible for its deallocation. Particularly, in the event of a leave, the memory must
be owned by a variable in scope after the leave, or by the global cleanup stack, but not by
both. Due to interactions with the Symbian cleanup stack, this fundamental property of
ownership can be violated by a few potential defect types:
• A leaving function is called and the memory is not owned by the cleanup stack or any
variable that is in scope after the leave.
Potential defect: Allocated memory is simply leaked when its owner goes out of scope as
the result of a leave.
• Allocated memory is pushed onto the cleanup stack more than once.
Potential defect: The memory is double-freed because it has more than one owner, both of
which deallocate it.
In the Prevent 4.1 release, the checker also verifies the following two properties:
11
• Functions exit with a net addition of zero elements on the cleanup stack, or one element if
the function has an ‘LC’ suffix (this requires the multiple_pushes checker option).
• When Pop() and PopAndDestroy() function arguments are specified, the correct
argument is popped from the cleanup stack (this requires the bad_pop checker option).
By tracking these events appropriately, the checker can also determine when defects occur in
the code, such as:
• double_free — An object is freed twice or is freed while it is still on the global cleanup
stack.
• double_push — An object is pushed more than once onto the cleanup stack.
• leave_without_push — A leaving function is called without a push to global cleanup
stack.
• memory leak — Memory is not appropriately deallocated before the pointer goes out of
scope.
• bad_pop_arg — The argument to a popping function does not match argument that is
being popped from the cleanup stack (in Prevent 4.1).
• more_than_one_push — More than one allocation is pushed onto the cleanup stack
along a path in a function (in Prevent 4.1).
Developed by Coverity with input from Symbian, this checker successfully identifies critical
defects. Symbian actively uses the SYMBIAN.CLEANUP_STACK checker internally and continues
to collaborate with Coverity to further improve its accuracy and capabilities. The checker is
included in the Coverity Prevent 4.0 release so that you may use the checker in a test
environment and evaluate its results. Users that do not have this checker must enable it by
using the --symbian option to cov-analyze.
...
78 void CWapPortWatcher::DoReceiveL(const TDesC8& aRecvFrom)
79 {
80 BIOWATCHERLOG(iWatcherLog.Printf(_L("BioWap: DoReceiveL: %S, port %d"),
&iBioMsgText, iWapPort));
81
82 // Code for collecting message goes here...
83 BIOWATCHERLOG(iWatcherLog.Printf(_L8("BioWap: Recv datagram length:
%d on socket OK"), iRecvBuf->Length()));
84
85 // Create a CSmsMessage from the received buffer
Event alloc_fn: Called allocation function "CSmsBuffer::NewL()" [details]
Event assign: Assigning "CSmsBuffer::NewL()" to "buffer"
12
86 CSmsBuffer* buffer=CSmsBuffer::NewL();
Event push: Pushing "buffer" onto cleanup stack
87 CleanupStack::PushL(buffer);
88
Event alias: Argument "buffer" aliased in function
"CSmsMessage::NewL(this->iFs, N7CSmsPDU11ESmsDeliverE, buffer, 0)"
[details]
Event double_push: Object "buffer" being pushed onto cleanup stack is
already on the stack [details]
89 CSmsMessage* smsmessage = CSmsMessage::NewL(iFs, ESmsDeliver, buffer);
90 CleanupStack::PushL(smsmessage);
...
Explanation of defect: Here, buffer is first pushed on the cleanup stack in line 87.
In the Defect Manager, clicking on the ‘details’ hyperlink takes you to the
CSmsMessage::NewL() method (shown below), where the buffer is again pushed onto the
cleanup stack at line 81. If new(ELeave) CSmsMessage()at line 82 within
CSmsMessage::NewL() were to leave, this would result in a double deletion of buffer,
which in turn could result in a KERN-EXEC 3 panic. If this were to occur in a system-critical
executable, it would reboot the device.
...
Event alias: Parameter "aBuffer" might be aliased in function
""CSmsMessage::NewL(RFs &, CSmsPDU::TSmsPDUType, CSmsBufferBase *, int)""
77 EXPORT_C CSmsMessage* CSmsMessage::NewL(RFs& aFs,
CSmsPDU::TSmsPDUType aType,
CSmsBufferBase* aBuffer,
TBool aIsRPError)
78 {
79 LOGGSMU1("CSmsMessage::NewL()");
80
81 CleanupStack::PushL(aBuffer);
82 CSmsMessage* smsmessage=new(ELeave) CSmsMessage(aFs, aBuffer);
83 CleanupStack::Pop();
84 CleanupStack::PushL(smsmessage);
85 smsmessage->ConstructL(aType,aIsRPError);
86 CleanupStack::Pop();
87 return smsmessage;
88 }
...
Checker: SYMBIAN.NAMING
In the release of Coverity Prevent 4.1, the SYMBIAN.NAMING checker verifies that naming
conventions mandated by Symbian are followed properly. Failure to follow these naming
conventions can lead to serious defects in understanding the behavior of a function. In its
current form, the naming rules that the checker enforces include:
• Functions that can potentially leave must contain ‘L’ in their suffix.
• Functions that push a pointer onto the cleanup stack must contain ‘LC’ in their suffix (if an
option is enabled).
13
• assign — A pointer is assigned a value from another pointer or from a function that
returns allocated memory.
• identity — A method returns one of its arguments.
• leave — A leaving function is called.
• pop — Pop the element off the cleanup stack.
• push — Push the element onto the cleanup stack.
By tracking these events appropriately, the checker can also determine when naming
convention violations appear in the code using the naming_error event.
This checker successfully identifies potentially critical defects. Symbian actively uses the
SYMBIAN.NAMING checker internally and continues to collaborate with Coverity to further
improve its accuracy and capabilities. The checker is included in the Coverity Prevent 4.1
release so that you may use the checker in a test environment and evaluate its results. Users
that do not have this checker must enable it using the --symbian option to cov-analyze.
...
35 void CCaptureKeys::CheckCaptureKey(const TCaptureKey& aCaptureKey)
36 {
37
38 if((aCaptureKey.iModifiers.iValue&~aCaptureKey.iModifiers.iMask)!=0)
Event leave: Leaving function "User::Leave(int)" called
39 User::Leave(KErrArgument);
40 }
41
...
73 EXPORT_C void CCaptureKeys::SetCaptureKey(TUint32 aHandle,
const TCaptureKey& aCaptureKey,
TUint8 aPriority)
74 //
75 // Finds the first capture-key from the list that matches the handle
76 // and sets it to the new value.
77 //
78 {
79
80 TCaptureKey captureKey(aCaptureKey);
81 captureKey.iKeyCodePattern.iFiller = aPriority;
Event naming_error: Leaving function CheckCaptureKey called in this
function whose name, CCaptureKeys::SetCaptureKey(unsigned long, const
TCaptureKey &, unsigned char), does not follow the naming convertion of
having an 'L' suffix [details]
82 CheckCaptureKey(captureKey);
83 TCaptureKey ck;
...
there is another error since CheckCaptureKey() itself violates the naming convention. It
ought to have an ‘L’ suffix too. Violating naming conventions can result in erroneous
assumptions made by callers of functions. In this case, callers of SetCaptureKey() (and
CheckCaptureKey()) are not aware that the function can leave and may not manage
memory ownership correctly in the event of an unexpected leave.
Checker: RETURN_LOCAL
The RETURN_LOCAL checker finds instances where a function returns a pointer to a local stack
variable. In C and C++, all local variables are lost upon function exit, as a stack frame is
removed and control is returned to the calling function. Variables that were allocated on the
stack inside the function are no longer relevant when it returns; their memory will be
overwritten when a new function is called. Pointers to local stack variables that are returned
to a calling function can cause memory corruption and inconsistent behavior. This checker
finds instances where a function returns a pointer to a stack-allocated variable.
...
7980 void* ptr = NULL;// Either TInetRouteInfo or TInetNeighbourInfo
7981 TTime stamp;
7982 stamp.UniversalTime();
7983
7984 if (evclass == EClassRoute)
7985 {
7986 TInetRouteInfo rinfo;
7987 aRoute->FillRouteInfo(rinfo, Elapsed(stamp));
7988 ptr = &rinfo;
7989 }
7990 else
7991 {
7992 TInetNeighbourInfo nginfo;
7993 aRoute->FillNeighbourInfo(nginfo, Elapsed(stamp));
Event local_ptr_assign_local: Assigning address of stack variable
"nginfo" to pointer "ptr"
7994 ptr = &nginfo;
Event out_of_scope: Variable "nginfo" goes out of scope
7995 }
7996
Event use_invalid: Used "ptr" pointing to out-of-scope variable
"nginfo"
7997 mgr.EventManager()->Notify(evclass, aEventType, ptr);
...
Explanation of defect: Along the path depicted in the defect above, ptr is assigned the
address of a local variable, nginfo (line 7994). However, when nginfo goes out of scope
(line 7995), its location on the stack can be used for other variables. Thus when ptr is used
in the function Notify() (line 7997), the function could access an out-of-scope or stale
location, leading to unreliable results.
15
Checker: NULL_RETURNS
The NULL_RETURNS checker finds instances of unchecked dereferences of NULL return values.
We sometimes do not test function return values, and instead use them in potentially
dangerous ways. Every time a variable is assigned the return value of a function that could
potentially return a NULL pointer, that variable must be checked against NULL before it can
be considered safe to use. Failing to check NULL pointer return values can cause crashes due
to NULL dereferencing.
...
776 // Normal case - send the On request
777 HWRMFmTxCommand::TSetFrequencyPackage pckg(iFmTxCommonData.Frequency());
Event returned_null: Function
"CHWRMPluginTransactionList::FindTransaction(unsigned char, int)"
returned NULL value (checked 12 out of 13 times) [details]
Event var_assigned: Variable "data" assigned to NULL return value from
"CHWRMPluginTransactionList::FindTransaction(unsigned char, int)"
778 THWRMPluginRequestData* data = static_cast<THWRMPluginRequestData*>
779 ( iWatcherTransactionList->FindTransaction( aTransId, EFalse ) );
780
Event dereference: Dereferencing possibly NULL "&(data)->
iRequestMessage" in call to function
"CHWRMFmTxService::ExecutePluginCommandL(const RMessage2 &,
HWRMFmTxCommand::TFmTxCmd, int, TDesC8 &)" [details]
781 TRAP( pluginErr, ExecutePluginCommandL( data->iRequestMessage,
HWRMFmTxCommand::ETxOnCmdId,
EFalse,
pckg ) );
...
Checker: CHECKED_RETURN
The CHECKED_RETURN checker finds instances of inconsistencies in the way function return
values are handled. For example, it detects the case where the code neglects to handle an
error code returned from a system call. Ignoring returned function error codes and assuming
an operation's success can cause incorrect program behavior and even, in some cases,
system crashes. Here is an example defect, which is not a Symbian C++ example, but which
clearly illustrates the potential problem.
...
01 void usual_function_1() {
02 int rv = function_with_error_code();
03 if (rv == -1) {
04 handle_error():
05 }
06 }
07
16
08 void usual_function_2() {
09 int rv = function_with_error_code();
10 if (rv == -1) {
11 handle_error():
12 }
13 }
13 void unusual_function() {
Event check_return: Called function “function_with_error_code” whose
return value should be checked (Checked 2 out of 3 times
Event unchecked_value: Return value of “function_with_error_code” is
not checked
14 function_with_error_code();
15 }
...
Explanation of defect: The checker first statistically notices that in a majority of cases the
return value of the function, function_with_error_code() is checked. In the function
unusual_function(), the return value of function_with_error_code() is not saved
and checked. The effect of this defect is that a potential error condition is being ignored by
the function, which can cause erroneous behavior downstream after this function is called.
Checker: EVALUATION_ORDER
The EVALUATION_ORDER checker reports defects when it finds order-of-evaluation problems,
also known as sequence point violations. Because the C and C++ programming languages do
not specify the evaluation order of some sub expressions, you can see different behavior
when using a different compiler, compiler version, or compiler optimizer, or when running the
program on a different platform.
In the following example, the value of b after the assignment is 3 if the left side of the
operator is evaluated first, or 4 if the right side of the operator is evaluated first:
a = 1;
b = a + (a=2);
EVALUATION_ORDER looks for the following sequence points after each statement:
• comma (,)
• logical AND (&&)
• logical OR (||)
• conditional (? :)
...
966 case 2: // Up
967 {
968 TUint8* altDataPtr = (iBuffer == 1) ? &ilineDes2[1] :
&ilineDes1[1];
969
970 while (aDataPtr < aDataPtrLimit)
Event read_write_order: In "*aDataPtr++ = (TUint8)(*aDataPtr +
*altDataPtr++)", "aDataPtr" is read in "(TUint8)(*aDataPtr +
*altDataPtr++)" and written in "*aDataPtr++" (as assignment LHS) but
17
Explanation of defect: The C++ standard states that there is no sequence point in the middle
of the statement on line 971. Thus, the read of aDataPtr on the right hand side of the
assignment and the update operation to aDataPtr on the left hand side of the assignment
can occur in different orders for different compilers, or even for different versions of the same
compiler. This makes this code unreliable in the wake of upgrades and changes to the
compiler. Changes to the order of the read and the update operation can result in difficult-to-
detect logical bugs and even crashes (since it involves pointer dereferencing).
Checker: DEADCODE
The DEADCODE checker finds instances of code that can never be reached due to branches
whose condition will evaluate exactly the same each time. This checker does not warn about
function-level dead code, such as static functions that are never called.
Faulty code assumptions or other logic errors are often responsible for dead code. These
defects can have a broad range of effects. At best, dead code increases the size of source
code (and associated binaries). More seriously, logic errors can cause important code to never
execute, which can adversely affect program results or cause a program to crash.
Some dead code might be intentional. For example, defensive error checks may cause some
unreachable error paths, but they are included to guard against future changes. Also, code
that uses #if preprocessor statements — to conditionally compile different blocks for
different configurations — might have dead code in certain configurations.
Fixing these defects depends on what the code was intended to do. Removing truly dead
code eliminates the defect. For example:
...
Event const: After this line, the value of "refreshRate" is equal to
1000000
Event assignment: Assigning "1000000" to "refreshRate"
339 TInt refreshRate = 1000000;
340 _LIT(KDebugBar, "DEBUGBAR");
341 if (WsIniFile->FindVar(refreshRate, KDebugBar))
342 {
Event dead_error_condition: On this path, the condition "refreshRate <
100000" could not be true
343 if (refreshRate < 100000)
Event dead_error_line: Cannot reach this line of code, beginning
"refreshRate ..."
344 refreshRate = 50000;
345 iDebugBar = CDebugBar::NewL(this,
TRect(TSize(CurrentScreenSize().iWidth, 16)),
refreshRate);
346 }
...
18
Explanation of defect: This is a very subtle problem uncovered with dead code. This defect
uncovers the fact that refreshRate is an IN only parameter to FindVar(). The incorrect
overload of FindVar() was used, which resulted in refreshRate never being modifiable
from its default value by FindVar().
If this defect were to occur in production code, it would result in the impossibility of
changing some screen configuration options on the device.
Checker: REVERSE_NEGATIVE
During development, correctly checking the bounds of a value before using it is often
overlooked. For example, mishandling negative integers can cause hard-to-find problems -
from memory corruption to security vulnerabilities. This checker finds instances of dangerous
integer use followed by a check against NEGATIVE. Two situations could cause this scenario:
• The programmer ‘knows’ the integer cannot be negative, in which case the check is
unnecessary and should be removed as it incorrectly indicates to other programmers that
the integer could be negative.
• The integer could actually be negative, and the check needs to occur before the dangerous
use.
Here is an example defect that is not a Symbian C++ example, but which clearly illustrates the
potential problem.
...
01 void simple_reverse_neg(int some_signed_int) {
Event negative_sink_in_call: Tracked variable “some_signed_int” used as
argument to function kmalloc
02 some_struct *x = kmalloc(some_signed_int, GFP_KERNEL);
Event check_after_sink: Variable “some_signed_int” compared to 0 after
use in a negative sink
03 if (some_signed_int < 0)
04 return error
05 }
...
Explanation of defect: In the example above, the checker notices that a signed value,
some_signed_int, is used as an argument to kmalloc(), which allocates memory (line 2).
The size argument to kmalloc()is a ‘negative sink,’ in that it should NOT be provided
negative values. Further along the path, we notice that some_signed_int is tested against
O (line 3), indicating that it could potentially be a negative value. This defect would result in
a crash within kmalloc().
19
Checker: USE_AFTER_FREE
Once memory has been freed, you cannot safely access it. The USE_AFTER_FREE checker finds
instances of freed memory use. Specifically, it finds many types of double frees and freed
pointer dereferences.
Double free defects occur when free() is called more than once with the same memory
address argument. Double freeing a pointer can result in memory free list corruptions and
crashes. Dereferencing a freed pointer is dangerous because the memory to which it points
may have been changed or may no longer be accessible.
In multi-threaded programs, double frees are especially dangerous because one thread could
allocate another's freed memory, resulting in difficult-to-track race conditions.
...
Event freed_arg: Pointer "this->iRegistry" freed by function "operator
delete (void *)"
216 delete iRegistry;
217
218 // De register Backup and Restore and cleanup memory
At conditional (0): "this->iBackupNotification" taking true path
219 if(iBackupNotification)
220 {
Event deref_arg: Dereferencing freed pointer "this->iRegistry" in call
to function "CFeatMgrFeatureRegistry::GetFeaturesFilePathAndName()"
[details]
221 TFileName temp(iRegistry->GetFeaturesFilePathAndName());
...
Explanation of defect: Along the path shown above, iRegistry is first freed (line 216) and
then dereferenced (line 221). At that point, this code could use iRegistry after it has been
freed, potentially causing memory corruption and/or a crash.
Checker: OVERRUN_STATIC
The OVERRUN_STATIC checker finds instances of overruns of constant-sized, stack-allocated
arrays.
One of the most common causes of stack corruption and security vulnerabilities is buffer
overrun. These occur when memory outside the bounds of a stack-allocated array is
manipulated, causing memory corruption. Among other things, this corruption can lead to
hard-to-locate memory inconsistencies and security holes which can allow an attacker to take
control of the system.
Buffer overruns are common because languages such as C and C++ are inherently unsafe.
Array and pointer references are not automatically bounds-checked; it is up to the
programmer to check a variable against logical bounds. This can get quite difficult when a
program's control flow involves pointers and indices passed between functions.
20
...
473 inline void SetReachableTime(TUint32 aTime)
474 /**
475 * Sets the value of reachable timer
476 * @param aTime The timer value
477 */
478 {
Event overrun-local: Overrun of static array "(*this).i" of size 8 at
position 11 with index variable "11"
479 i[11] = (TUint8)aTime;
480 i[10] = (TUint8)(aTime >> 8);
481 i[9] = (TUint8)(aTime >> 16);
482 i[8] = (TUint8)(aTime >> 24);
483 }
...
Explanation of defect: The checker detects the defect in the code above where an array of
size 8 (line 440), i, in the class, is indexed with values greater than or equal to 8 (line 479).
This can lead to memory corruption errors and security vulnerabilities due to buffer overrun.
Conclusion
Many analysts predict continued, rapid growth in the market for next-generation, data-enabled
smartphones.1 Meeting this increasing consumer demand requires a continued, rapid
innovation in mobile device application development. Smartphone functionality is becoming
more robust, even as the device form factor shifts or diminishes with hardware advances. As
this change takes place, consumers insist that their next-generation phones and applications
deliver the same reliability and security as previous generations – despite the exponential
increase in complexity facing mobile device software today, and the host of new potential
defects they pose. Symbian C++ developers recognize the importance of detecting and fixing
these critical defects to ensure the reliability and security of their code before releasing it into
the field, where errors can have a disastrous impact.
An effective method for ensuring the highest possible code integrity, static analysis enables
the automatic detection of crash-causing defects early in the development cycle, where they
are the easiest and least costly to eliminate. By optimizing code quality with static analysis,
you can increase your speed to market, reduce the development costs associated with
tedious manual code review, and prevent serious software failures that can limit the utility of
next-generation mobile devices. Ultimately, the successful implementation of static analysis
tools allows you to create more innovative, reliable mobile applications and stay at the
forefront of the dynamic Symbian OS environment.
Multi-core support The changing landscape of hardware, such as the emergence of multi-core
processors, introduces new concurrency defects in software. Look for static tools
that have the ability to detect issues such as race conditions, deadlocks and
thread blocks.
Scalability/performance ratio Many smartphone application developers have large code bases. The
analysis time required by a tool increases with the amount of code, but
effective static analysis tools should not require more than three times the
existing build time for a given code base.
Macro support A static analysis tool should work seamlessly with macros. Tools that
cannot understand macro expansions do not understand the true semantics
of the code that will ultimately be executed.
Integration with development A static analysis tool should be easy to integrate into the toolchain of all
tools, IDEs developer groups that may come to use it.
Custom defect detection For example, Symbian C++ can introduce slightly modified versions of
‘checkers’ common defects. A static tool should have the ability to allow developers
to create customer ‘checkers’ to identify these variant defect types. Custom
checkers can also be created to help ensure compliance with corporate or
industry coding standards.
Organizations that use an SDK for any static tool should ensure that it can
store extensions in a customer checker library so you can collaboratively
build, execute and maintain your checkers.
Automatic assignment of defects To ensure the appropriate developers are working to eliminate the defects
they are responsible for introducing, static tools should be able to send
automatic notifications as needed.
Automatic monitoring of defect To confirm that a defect discovered by a static analysis tool is fixed,
fixing development managers need a workflow that allows them to monitor its
progress.
Training and support A combination of early training and tuning can provide a springboard for
developer adoption. In many cases, a tools vendor will offer training
sessions to tune the analysis after installation, and increase accuracy.
22
Resources
Coverity Prevent Static Analysis for C/C++: www.coverity.com/html/prevent-for-c-c++.html
Symbian OS Explained
by Jo Stichbury
Symbian OS Internals
by Jane Sales
Published Booklets
A Guide to P.I.P.S.
Carbide.c++ v1.3
Coding Standards
Coding Tips
Creating Location-Aware Applications
Data Sharing Tips
Essential S60 - Developers’ Guide
Essential UIQ - Getting Started
Getting Started
Getting to Market
Java ME on Symbian OS
Localization
.NET Development on S60
Performance Tips
Platform Security for all
Quick Recipes Taster
Translated Booklets
Chinese Russian
Japanese Persian
Korean Italian
Spanish
Notes:
SDN++
Coverity Prevent
on Symbian OS
SDN++ Why? What? Where? How?
Symbian Press
Symbian Press publishes books designed to communicate
authoritative, timely, relevant and practical information
about Symbian OS and related technologies. Information
about the Symbian Press series can be found at
developer.symbian.com/books