OCLint

OCLint is a fantastic static code analysis tool for improving quality and reducing defects by inspecting C, C++ and Objective-C code.

I’m not going to copy-paste the rest of oclint.org here, check it out to see what the tool can do. In this article you will learn how to set it up and use for Mobile CI.

OCLint is often referred as a lint tool, but in the end of the day it’s a static analysis tool, meaning it analyses your code statically, that is without running it.

Install

For quite a while I though OCLint wasn’t available via Homebrew package manager. While it technically isn’t there yet, it’s supported by Homebrew Cask. If you never used it yet, Homebrew Cask is an extension for Homebrew and lets you to manage OS X applications just like packages. Another benefit of this tool is that it includes other packages like OCLint which are not yet available in Homebrew.

brew install caskroom/cask/brew-cask

Now install OCLint

brew cask install oclint

Check the installation, it’s now sitting in usr/local/.

Use

I will write about using OCLint with xcodebuild, basically rewording official documentation. OCLint supports all other tools, check out documentation for details.

Using OCLint with xcodebuild requires 3 steps.

  • Build and capture output.
  • Generate compile commands database (JSON format) for OCLint using xcodebuild output.
  • Run OCLint using generated JSON database.

The first step is straightforward, run xcodebuild the way you would run it normally, the only difference is that you want to capture build output. Use tee command to do that, for example

xcodebuild clean build -project MyProject -scheme MyScheme -configuration Debug | tee xcodebuild.log

It is recommended to use Debug configuration for static analysis.

Next, generate compilation commands database with oclint-xcodebuild command. This command expects a file named xcodebuild.log by default, but in the example I will pass it explicitly. The default output file name is compile_commands.json but I’ll be more verbose anyway and specify output file name.

oclint-xcodebuild -output compile_commands.json xcodebuild.log

Optionally you can use -e | -exclude option and pass regular expression which will exclude the files you are not interested in, such as 3rd party source code.

Finally, use oclint-json-compilation-database to run oclint and generate report.

oclint-json-compilation-database \
  -exclude ${LINT_EXCLUDES} \
  -- \
  -report-type ${LINT_REPORT_TYPE} \
  ${LINT_RULES} \
  ${LINT_DISABLE_RULES} \
  ${LINT_THRESHOLD} \
  -o ${LINT_REPORTS_DIR}/${LINT_REPORT_FILE} \
  -stats \
  -verbose \
  -list-enabled-rules

Let’s analyze this call in details and expand actual values of all environment variables.

First you call oclint-json-compilation-database and specify it’s own arguments. In this case it is a list of excludes passed as ${LINT_EXCLUDES}. OCLint understands grep-like regular expressions syntax, for example

LINT_EXCLUDES="Libraries|lib|Pods|Carthage"

The -i [INCLUDES] option is used to do the opposite to exclude.

The double dash -- indicates the start of arguments which will be passed to invocation of oclint command.

Report type (LINT_REPORT_TYPE) specified via -report-type can be one of text, html, xml, json and pmd. For CI tasks you should definitely use PMD report type. While running locally stick with html. So you should set LINT_REPORT_TYPE to one of these values. Read the full documentation for more.

Lint rules (LINT_RULES) is an option with which you can override default threshold values for rules. For example, Objective-C is very verbose language, so let’s increase thresholds for the the max length of line of code, name of method and name of variable. This is how it will look in shell script

# Rules.
LINT_LONG_LINE=300
LINT_LONG_VARIABLE_NAME=64
LINT_LONG_METHOD=150

LINT_RULES="-rc LONG_LINE=${LINT_LONG_LINE} \
      -rc LONG_VARIABLE_NAME=${LINT_LONG_VARIABLE_NAME} \
      -rc LONG_METHOD=${LINT_LONG_METHOD}"

You may want to disable some rules as well. This could be useful when working with some legacy code which smells a lot and there are no plans to fix it ever. In cases like this it’s easier to silent some warnings. For example, let’s disable warning about the use of _ivarName outside of accessors and initializers. Let’s also ignore useless parentheses and warnings about unused method parameters.

# Disable rules.
LINT_DISABLE_RULES="-disable-rule=UnusedMethodParameter \
            -disable-rule=UselessParentheses \
            -disable-rule=IvarAssignmentOutsideAccessorsOrInit"

Full list of rules for further reference.

Note!: The names of the rules on documentation page do not match the names you should be using to disable them. For example CoveredSwitchStatementsDontNeedDefault, should be specified as SwitchStatementsDon\'TNeedDefaultWhenFullyCovered when used in command line scripts (note escaping the ' too). To get the correct names use --list-enabled-rules option, which is mentioned further in the post.

Threshold allows you to control when analysis passes or fails. There are 3 priorities (Priority 1, 2 and 3) and each comes with default thresholds (0, 10 and 20). You can use -max-priority-<N> command line option to customize priority N threshold. This is how you would change it to 0, 20 and 30 correspondingly

# Threshold.
LINT_PRIORITY_1_THRESHOLD=0
LINT_PRIORITY_2_THRESHOLD=20
LINT_PRIORITY_3_THRESHOLD=30
LINT_THRESHOLD = "-max-priority-1=${LINT_PRIORITY_1_THRESHOLD} \
    -max-priority-2=${LINT_PRIORITY_2_THRESHOLD} \
    -max-priority-3=${LINT_PRIORITY_3_THRESHOLD}"

Output can be specified via -o option. If you are using PMD report type, write out to XML file, for HTML report, write to HTML format. Better create a separate directory for reports, for example

# Reports.
LINT_REPORTS_DIR=oclint-reports
# Use .html for HTML report type.
LINT_REPORT_FILE=oclint.xml

mkdir -p ${LINT_REPORTS_DIR}

Finally add some verbosity with -stats to output statistics, -list-enabled-rules to eyeball the rules being used and -verbose for what it stands.

Config File

Instead of having lengthy shell scripts, you can opt out to using .oclint config file. Put it in you project’s root folder and list all the thresholds and other rules configs it YAML format, for example:

# OCLint config example

rules:
disable-rules:
  - GotoStatement
  - UnusedMethodParameter
  - EmptyIfStatement
  - SwitchStatementsDon'TNeedDefaultWhenFullyCovered

rule-configurations:
  - key: LONG_LINE
    value: 300
  - key: SHORT_VARIABLE_NAME
    value: 1
  - key: LONG_VARIABLE_NAME
    value: 64

max-priority-1: 0
max-priority-2: 20
max-priority-3: 30

enable-clang-static-analyzer: false

Read official documentation for more details.

Other Notes

Integrate with Xcode

There are ways to integrate OCLint with Xcode. This is actually worth a separate post on its own. The goal is to allow non-script-and-command-line-savvy teammates to run OCLint analyzer in Xcode. Don’t set your hopes too hight though, they just won’t do that anyway :) However, check this post for some examples.

Suppress Warnings

Sometimes you just have to violate the rule. Let’s say it’s a legacy code base and there’s just this one violation and nothing you can do about it. Instead of disabling a useful rule completely, consider suppressing the warning.

Note: 0.10.1 doesn’t understand //!OCLint suppress comments. It is case-sensitive and must be //!OCLINT. This should be fixed in next releases though.

Save Time

Running a project build to generate Xcode build log, then running OCLint is very time consuming task. Consider generating Xcode build log once and then reusing it for OCLint. You will have to generate fresh build log when the project structure changes.

If you run unit tests as part of CI pipeline, then capture build log when building unit tests target.

Summary

Give it a go. You now have a very powerful tool in your tool belt. Figure out your custom project thresholds, integrate it as part of your project makefile or Fastfile, run it as CI job and output reports using one of the plugins available.

You may actually unearth a pair of real bugs in your code.

Published: February 08 2015

blog comments powered by Disqus