Compare commits

...

10 commits

Author SHA1 Message Date
Alexander Kobjolke
5a55e96615 Merge branch 'release/0.0.5.2' into 'main'
Bump version to 0.0.5.2

See merge request kobjolke_a/annotator!4
2023-01-06 14:37:16 +00:00
Alexander Kobjolke
687b0ef632 Bump version to 0.0.5.2 2023-01-06 15:36:42 +01:00
Alexander Kobjolke
3019f4ddc5 Merge branch 'fix/hlint-hints' into 'main'
Fix all found hlint hints

See merge request kobjolke_a/annotator!3
2023-01-06 14:19:52 +00:00
Alexander Kobjolke
c70ce7c1d8 Fix all found hlint hints 2023-01-06 15:17:10 +01:00
Alexander Kobjolke
c9b6ad7427 Merge branch 'doc/use-org-mode-for-readme' into 'main'
Use org mode for readme

See merge request kobjolke_a/annotator!2
2023-01-06 13:50:03 +00:00
Alexander Kobjolke
de1be0aba3 doc: Remove TODO markers from example code 2023-01-06 14:48:31 +01:00
Alexander Kobjolke
737ff6afe3 doc: Replace README.md by README.org 2023-01-06 14:48:31 +01:00
Alexander Kobjolke
f55a6c71b6 Merge branch 'feature/gitlab-ci' into 'main'
ci: Fix hlint CodeClimate output

See merge request kobjolke_a/annotator!1
2023-01-05 13:41:19 +00:00
Alexander Kobjolke
8b4754e9ad ci: Fix hlint CodeClimate output 2023-01-05 13:41:18 +00:00
Alexander Kobjolke
59eb3b502e ci: Fix ci script 2023-01-05 12:17:34 +01:00
9 changed files with 131 additions and 156 deletions

View file

@ -29,7 +29,8 @@ build-job: # This job runs in the build stage, which runs first.
script: script:
- make release - make release
artifacts: artifacts:
- build/dist/*.tar.gz paths:
- build/dist/*.tar.gz
unit-test-job: # This job runs in the test stage. unit-test-job: # This job runs in the test stage.
stage: test # It only starts when the job in the build stage completes successfully. stage: test # It only starts when the job in the build stage completes successfully.
@ -38,7 +39,7 @@ unit-test-job: # This job runs in the test stage.
- linux - linux
script: script:
- echo "Running unit tests... This will take about 60 seconds." - echo "Running unit tests... This will take about 60 seconds."
- sleep 60 - sleep 1
- echo "Code coverage is 90%" - echo "Code coverage is 90%"
lint-test-job: # This job also runs in the test stage. lint-test-job: # This job also runs in the test stage.

View file

@ -1,27 +1,32 @@
.PHONY: default all install watch clean build release distclean lint .PHONY: default all install watch clean build release distclean lint doc
default: all default: all
all: build all: build
build: builddir build/bin/annotator
GHCFLAGS = -iapp -isrc -Wall -O2 -outputdir build/.obj GHCFLAGS = -iapp -isrc -Wall -O2 -outputdir build/.obj
BUILDDIR = build BUILDDIR = build
DESTDIR = ~/.local DESTDIR = ~/.local
BINDIR = ${DESTDIR}/bin BINDIR = ${DESTDIR}/bin
DOCDIR = ${DESTDIR}/share/doc/annotator DOCDIR = ${DESTDIR}/share/doc/annotator
build: builddir | $(BUILDDIR)/bin/annotator doc
builddir: builddir:
mkdir -p $(BUILDDIR)/bin $(BUILDDIR)/.obj $(BUILDDIR)/ci $(BUILDDIR)/dist mkdir -p $(BUILDDIR)/{bin,.obj,ci,dist,doc}
doc: builddir | $(BUILDDIR)/doc/README.md
$(BUILDDIR)/bin/annotator: app/Main.hs src/*.hs $(BUILDDIR)/bin/annotator: app/Main.hs src/*.hs
ghc --make $(GHCFLAGS) $< -o $@ ghc --make $(GHCFLAGS) $< -o $@
$(BUILDDIR)/doc/README.md: README.org
pandoc -t gfm $< -o $@
watch: watch:
@git ls-files src app Makefile | entr make -s build install @git ls-files src app Makefile | entr make -s build install
lint: lint: builddir
@-hlint -g --cc > $(BUILDDIR)/ci/hlint.report.json hlint lint --git --cc --no-exit-code | tr -d '\0' | jq -s . > $(BUILDDIR)/ci/hlint.report.json
clean: clean:
@rm -rf $(BUILDDIR) @rm -rf $(BUILDDIR)
@ -31,9 +36,10 @@ clean:
distclean: clean distclean: clean
@rm -rf dist @rm -rf dist
install: $(BUILDDIR)/bin/annotator install: build
@install -m 0755 -D -t ${BINDIR} $< @install -m 0755 -D -t ${BINDIR} $(BUILDDIR)/bin/annotator
@install -m 0644 -D -t ${DOCDIR} README.md @install -m 0644 -D -t ${DOCDIR} README.org
@install -m 0644 -D -t ${DOCDIR} $(BUILDDIR)/doc/README.md
release: script/make-release release: script/make-release
@$< @$<

View file

@ -1,84 +0,0 @@
# Annotator - an interactive TM2/TM3 annotation tool
This tool lets you interactively annotate your code given a =defects.err= file.
## Installation
Get the most recent release, unpack it and add the binary to your PATH.
## How to use it
After a /Coverity™/ run, you end up with a =defects.err= and would like to
annotate your code accordingly. In order to do so, just execute the =annotator=:
```
annotator
```
This will by default use the =defects.err= file in the current directory and
scan it for violations. It will then ask you what to do with each violation - by
default only /Newest/ violations will be handled, but this can be overridden
with a command-line switch.
After all violations have been treated, you'll end up with a bunch of =*.fix=
files next to each source file - those are the annotated source files - you may
run diff on them to check if they look fine or just move them over your original
source file.
### Possible annotations
The annotator is able to generate three kinds of annotations:
- *Intentional* - by pressing `i`, this will annotate with `coverity[rule] <reason>`
- *False-Positive* - by pressing `f`, this will annotate with `coverity[rule : FALSE] <reason>`
- *Todo* - by pressing `t`, this will annotate with a FIXME marker
## Advanced usage scenarios
The current annotator supports the following command-line arguments:
```
$ annotator --help
Usage: annotator [OPTIONS] files...
A tool to semi-automatically add Coverity source-code annotations based on found defects.
-v --verbose be more verbose, pass multiple times to increase verbosity
-i --inplace replace source-file after inserting annotations
-V --version show full version information
--short-version show just the version number
-h --help show usage information
-a --all handle all defects not just Newest
-C[NUM] --context[=NUM] specify how much context should be shown around a violation
-t[STRING] --todo-marker[=STRING] override the default TODO marker with a custom string
-A FILE --annotations=FILE load automatic annotation rules
```
### In-place annotations
The `annotator` allows to annotate in-place, i.e. it will automatically rename
the `.fix` file after you are done with all violations within that file. This
can be achieved by passing `-i` or `--inplace`.
### Process all violations
By default the annotator will only handle *Newest* violations and not those that
are already contained within the Coverity database for some reason. However,
it's still possible to process all found violations by passing `-a` or `--all`
on the command-line.
### Insert annotations automatically
In case you have violations that always result in the same annotation over and
over again, you can supply one or more files that contain automatic decisions.
Each line may be one of the following:
```
Intentional (Rule "autosar_cpp14_a18_9_1") "reason why it's intentional"
FalsePositive (Rule "autosar_cpp14_a18_9_1") "reason why it's a false-positive"
ToDo (Rule "autosar_cpp14_a18_9_1")
```
Lines may be disabled by prefixing them with `#` or `--`.

View file

@ -1,14 +1,54 @@
#+title: Readme #+title: Annotator - an interactive Coverity annotation tool
* Abstract
This tool lets you interactively annotate your code given a =defects.err= file. The file should contain file and line information along with the Coverity rule that was violated. An example may look like this:
#+begin_example csv
main.cpp:162:INFO: Newest, autosar_cpp14_a20_8_6_violation: Object "std::unique_ptr<Configuration const, std::default_delete<Configuration const> >(read_configuration(uri))" is not constructed using "std::make_shared".
#+end_example
* Installation * Installation
Get the most recent release, unpack it and add the binary to your PATH.
* Usage * Usage
#+begin_src sh :results output After a /Coverity™/ run, you end up with a =defects.err= and would like to
annotate your code accordingly. In order to do so, just execute the =annotator=:
#+begin_src sh
annotator
#+end_src
This will by default use the =defects.err= file in the current directory and
scan it for violations. It will then ask you what to do with each violation - by
default only /Newest/ violations will be handled, but this can be overridden
with a command-line switch.
After all violations have been treated, you'll end up with a bunch of =*.fix=
files next to each source file - those are the annotated source files - you may
run diff on them to check if they look fine or just move them over your original
source file.
** Possible annotations
The annotator is able to generate three kinds of annotations:
- Intentional :: by pressing =i=, this will annotate with =coverity[rule] <reason>=
- FalsePositive :: by pressing =f=, this will annotate with =coverity[rule : FALSE] <reason>=
- Todo :: by pressing =t=, this will annotate with a TODO marker
* Advanced usage scenarios
The current annotator supports the following command-line arguments:
#+name: annotator-help
#+begin_src sh :results output :exports both
annotator --help annotator --help
#+end_src #+end_src
#+RESULTS: #+RESULTS: annotator-help
#+begin_example #+begin_example
Usage: annotator [OPTIONS] files... Usage: annotator [OPTIONS] files...
@ -32,3 +72,25 @@ A tool to semi-automatically add Coverity source-code annotations based on found
# a comment # a comment
-- another comment -- another comment
#+end_example #+end_example
** In-place annotations
The =annotator= allows to annotate in-place, i.e. it will automatically rename the =.fix= file after you are done with all violations within that file. This can be achieved by passing =-i= or =--inplace=.
** Process all violations
By default the annotator will only handle *Newest* violations and not those that are already contained within the Coverity database for some reason. However, it's still possible to process all found violations by passing =-a= or =--all= on the command-line.
** Insert annotations automatically
In case you have violations that always result in the same annotation over and over again, you can supply one or more files that contain automatic decisions.
Each line may be one of the following:
#+begin_example haskell
Intentional (Rule "autosar_cpp14_a18_9_1") "reason why it's intentional"
FalsePositive (Rule "autosar_cpp14_a18_9_1") "reason why it's a false-positive"
ToDo (Rule "autosar_cpp14_a18_9_1")
#+end_example
Lines may be disabled by prefixing them with =#= or =--=.

View file

@ -4,7 +4,7 @@
#include <asap/asap.hpp> #include <asap/asap.hpp>
#include <process/process_collection.hpp> #include <process/process_collection.hpp>
#include "process_definition.hpp" // TODO generate to <process/{idl/}process_definition.hpp> #include "process_definition.hpp"
namespace process::controller { namespace process::controller {
class DefinitionListener; class DefinitionListener;

View file

@ -1,7 +1,7 @@
#ifndef PROCESS_CONTROLLER_PREDICATE_HPP #ifndef PROCESS_CONTROLLER_PREDICATE_HPP
#define PROCESS_CONTROLLER_PREDICATE_HPP #define PROCESS_CONTROLLER_PREDICATE_HPP
#include "process_definition.hpp" // TODO: generate to <build>/idl/process/process_definition.hpp #include "process_definition.hpp"
#include <string> #include <string>
#include <functional> #include <functional>
@ -62,8 +62,6 @@ namespace predicate {
/** /**
* returns a function that checks whether the node of a ProcessDefinition matches a given pattern * returns a function that checks whether the node of a ProcessDefinition matches a given pattern
*
* TODO implement more sophisticated glob matching, we currently support '*' and exact match
*/ */
[[nodiscard]] inline std::function<bool(const ProcessDefinition&)> node_matches(const std::string& pattern) noexcept [[nodiscard]] inline std::function<bool(const ProcessDefinition&)> node_matches(const std::string& pattern) noexcept
{ {

View file

@ -1,7 +1,6 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE InstanceSigs #-} {-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE BangPatterns #-} {-# LANGUAGE StrictData #-}
module Annotator (defaultMain) where module Annotator (defaultMain) where
@ -17,7 +16,7 @@ import Control.Monad.IO.Class
import Control.Monad.Trans.Reader import Control.Monad.Trans.Reader
import Control.Exception (evaluate) import Control.Exception (evaluate)
import Data.Function (on) import Data.Function (on)
import Data.List (delete, intercalate, foldl', sortBy, nub) import Data.List (delete, foldl', sortOn, nub)
import Data.Char (isSpace) import Data.Char (isSpace)
import Data.Either (rights) import Data.Either (rights)
import Data.Ord import Data.Ord
@ -31,7 +30,7 @@ import Annotator.Rule
import Annotator.Annotation import Annotator.Annotation
version :: Vsn.Version version :: Vsn.Version
version = Vsn.makeVersion [0,0,5,1] version = Vsn.makeVersion [0,0,5,2]
type App a = ReaderT Options IO a type App a = ReaderT Options IO a
@ -104,7 +103,7 @@ options =
(ReqArg (\f opts -> opts { todoMarker = f }) "STRING") (ReqArg (\f opts -> opts { todoMarker = f }) "STRING")
"override the default TODO marker with a custom string" "override the default TODO marker with a custom string"
, Option ['A'] ["annotations"] , Option ['A'] ["annotations"]
(ReqArg (\f opts -> opts { annotationFiles = (nub $ annotationFiles opts ++ [f]) }) "FILE") (ReqArg (\f opts -> opts { annotationFiles = f : annotationFiles opts }) "FILE")
(unlines [ "load automatic annotation rules" (unlines [ "load automatic annotation rules"
, " some examples:" , " some examples:"
, " " <> show (Intentional (Rule "rule_1") "some reason") , " " <> show (Intentional (Rule "rule_1") "some reason")
@ -124,7 +123,9 @@ readMaybe s = case reads s of
parseOptions :: [String] -> IO (Options, [String]) parseOptions :: [String] -> IO (Options, [String])
parseOptions argv = parseOptions argv =
case getOpt Permute options argv of case getOpt Permute options argv of
(o, n, []) -> pure (foldl' (flip id) defaultOptions o, n) (o, n, []) -> do
let o' = foldl' (flip id) defaultOptions o
pure (o'{annotationFiles = reverse $ nub $ annotationFiles o'}, n)
(_, _, errs) -> ioError (userError (concat errs ++ usageInfo header options)) (_, _, errs) -> ioError (userError (concat errs ++ usageInfo header options))
where where
header = "Usage: annotator [OPTIONS] files..." header = "Usage: annotator [OPTIONS] files..."
@ -145,18 +146,18 @@ defaultMain = do
Just FullVersion -> do Just FullVersion -> do
putStr $ unlines putStr $ unlines
[ [
"Annotator v" <> (Vsn.showVersion version) "Annotator v" <> Vsn.showVersion version
, "Copyright (c) 2022 Alexander Kobjolke <alexander.kobjolke@atlas-elektronik.com>" , "Copyright (c) 2022 Alexander Kobjolke <alexander.kobjolke@atlas-elektronik.com>"
] ]
exitWith ExitSuccess exitSuccess
Just ShortVersion -> do Just ShortVersion -> do
putStrLn $ Vsn.showVersion version putStrLn $ Vsn.showVersion version
exitWith ExitSuccess exitSuccess
Nothing -> pure () Nothing -> pure ()
when (showHelp opts) $ do when (showHelp opts) $ do
putStr $ usageInfo header options putStr $ usageInfo header options
exitWith ExitSuccess exitSuccess
automaticAnnotations <- rights . concat <$> (filterM fileExist (annotationFiles opts) >>= mapM fromFile) automaticAnnotations <- rights . concat <$> (filterM fileExist (annotationFiles opts) >>= mapM fromFile)
@ -165,9 +166,8 @@ defaultMain = do
runReaderT (genericMain fn) opts' runReaderT (genericMain fn) opts'
where where
header = unlines $ header = unlines
[ [ "Usage: annotator [OPTIONS] files..."
"Usage: annotator [OPTIONS] files..."
, "" , ""
, "A tool to semi-automatically add Coverity source-code annotations based on found defects." , "A tool to semi-automatically add Coverity source-code annotations based on found defects."
, "" , ""
@ -177,11 +177,11 @@ data Age = Old | New | Newest
deriving (Show, Eq) deriving (Show, Eq)
data Violation = Violation { data Violation = Violation {
filename :: !FilePath, filename :: FilePath,
line :: !Int, line :: Int,
age :: !Age, age :: Age,
rule :: !Rule, rule :: Rule,
description :: !String description :: String
} }
deriving (Show, Eq) deriving (Show, Eq)
@ -190,7 +190,7 @@ split _ _ [] = []
split n p xs split n p xs
| n > 0 = case break p xs of | n > 0 = case break p xs of
(match, []) -> [match] (match, []) -> [match]
(match, (_ : rest)) -> match : split (n-1) p rest (match, _ : rest) -> match : split (n-1) p rest
| otherwise = [xs] | otherwise = [xs]
parseViolations :: String -> [Violation] parseViolations :: String -> [Violation]
@ -208,7 +208,7 @@ parseViolation s = case split 4 (== ':') s of
where where
violation = Violation file (read line) a (Rule (removeSuffix "_violation" r)) (dropWhile isSpace $ concat desc) violation = Violation file (read line) a (Rule (removeSuffix "_violation" r)) (dropWhile isSpace $ concat desc)
(_:age':r:_) = split 2 (== ' ') rule' (_:age':r:_) = split 2 (== ' ') rule'
a = case (delete ',' age') of a = case delete ',' age' of
"Newest" -> Newest "Newest" -> Newest
"New" -> New "New" -> New
_ -> Old _ -> Old
@ -230,7 +230,7 @@ genericMain file = do
verbose Chatty $ "all violations: " <> show sortedViolations verbose Chatty $ "all violations: " <> show sortedViolations
forM_ groupedViolations handleViolations forM_ groupedViolations handleViolations
Nothing -> do Nothing ->
liftIO $ hPutStrLn stderr "Defects file is empty" liftIO $ hPutStrLn stderr "Defects file is empty"
where where
@ -249,10 +249,10 @@ handleViolations violations = do
let fname = filename $ NE.head violations let fname = filename $ NE.head violations
fname' = fname <> ".fix" fname' = fname <> ".fix"
todo = todoMarker opts todo = todoMarker opts
header' = "Processing " <> (show $ NE.length violations) <> " violation(s) in file " <> fname header' = "Processing " <> show (NE.length violations) <> " violation(s) in file " <> fname
header = unlines header = unlines
[ header' [ header'
, take (length header') $ repeat '=' , replicate (length header') '='
, "" , ""
] ]
@ -268,15 +268,15 @@ handleViolations violations = do
let numberedContent = zip [1..] . lines $ content let numberedContent = zip [1..] . lines $ content
annotations <- (catMaybes . NE.toList) <$> mapM (handleViolation content) violations annotations <- catMaybes . NE.toList <$> mapM (handleViolation content) violations
let annotatedLines = sortBy (comparing fst) $ fmap (annotationToLine todo) annotations let annotatedLines = sortOn fst $ fmap (annotationToLine todo) annotations
newContent = unlines . map snd $ mergeLines annotatedLines numberedContent newContent = unlines . map snd $ mergeLines annotatedLines numberedContent
liftIO $ writeFile fname' newContent liftIO $ writeFile fname' newContent
liftIO $ when (inplace opts) $ rename fname' fname liftIO $ when (inplace opts) $ rename fname' fname
else do else
verbose Low $ "skipping non-existent file " <> fname verbose Low $ "skipping non-existent file " <> fname
where where
annotationToLine :: String -> AnnotatedViolation -> (Int, String) annotationToLine :: String -> AnnotatedViolation -> (Int, String)
@ -296,24 +296,17 @@ data UserChoice = Abort
-- | let the user decide what to do with a violation -- | let the user decide what to do with a violation
getUserChoice :: Violation -> App UserChoice getUserChoice :: Violation -> App UserChoice
getUserChoice Violation{..} = do getUserChoice Violation{..} = liftIO queryUser
liftIO $ queryUser
where queryUser = do where queryUser = do
putStr $ "> What shall we do [s/t/i/f/q/?]: " putStr "> What shall we do [s/t/i/f/q/?]: "
hFlush stdout hFlush stdout
c <- getChar c <- getChar
putStrLn "" putStrLn ""
case c of case c of
's' -> do 's' -> pure Skip
pure Skip 't' -> pure $ Annotate (ToDo rule)
't' -> do 'i' -> Annotate . Intentional rule <$> getExcuse
pure $ Annotate (ToDo rule) 'f' -> Annotate . FalsePositive rule <$> getExcuse
'i' -> do
excuse <- getExcuse
pure $ Annotate (Intentional rule excuse)
'f' -> do
excuse <- getExcuse
pure $ Annotate (FalsePositive rule excuse)
'q' -> pure Abort 'q' -> pure Abort
'?' -> do '?' -> do
putStrLn $ unlines [ "t - add TODO marker to fix this issue" putStrLn $ unlines [ "t - add TODO marker to fix this issue"
@ -328,7 +321,7 @@ getUserChoice Violation{..} = do
_ -> queryUser _ -> queryUser
getExcuse = do getExcuse = do
putStr $ "What's your excuse? " putStr "What's your excuse? "
hFlush stdout hFlush stdout
mode <- hGetBuffering stdin mode <- hGetBuffering stdin
hSetBuffering stdin LineBuffering hSetBuffering stdin LineBuffering
@ -349,7 +342,7 @@ type NumberedLine = (Int, String)
mergeLines :: [NumberedLine] -> [NumberedLine] -> [NumberedLine] mergeLines :: [NumberedLine] -> [NumberedLine] -> [NumberedLine]
mergeLines [] r = r mergeLines [] r = r
mergeLines l [] = l mergeLines l [] = l
mergeLines !lhs@(left@(nl,_):ls) !rhs@(right@(nr,_):rs) mergeLines lhs@(left@(nl,_):ls) rhs@(right@(nr,_):rs)
| nl <= nr = left : mergeLines ls rhs | nl <= nr = left : mergeLines ls rhs
| otherwise = right : mergeLines lhs rs | otherwise = right : mergeLines lhs rs
@ -379,21 +372,21 @@ handleViolation content v@Violation{..} = do
-- print some context -- print some context
liftIO $ forM_ context (\(n, code) -> do liftIO $ forM_ context (\(n, code) -> do
let marker = ">>>>" let marker = ">>>>"
when (n == line) $ putStrLn (intercalate " " [ marker when (n == line) $ putStrLn (unwords [ marker
, show age , show age
, "violation of rule" , "violation of rule"
, show rule , show rule
, "in line" , "in line"
, show line <> ":" , show line <> ":"
, description , description
]) ])
putStrLn (code)) putStrLn code)
if batchMode opts if batchMode opts
then pure $ Nothing then pure Nothing
else do else do
choice <- getUserChoice v choice <- getUserChoice v
case choice of case choice of
Abort -> liftIO $ exitSuccess Abort -> liftIO exitSuccess
Annotate annotation -> pure $ Just (AnnotatedViolation v annotation indent) Annotate annotation -> pure $ Just (AnnotatedViolation v annotation indent)
Skip -> pure Nothing Skip -> pure Nothing
Help -> handleViolation content v Help -> handleViolation content v

View file

@ -1,4 +1,3 @@
{-# LANGUAGE LambdaCase #-}
module Annotator.Annotation (Annotation(..), fromFile) where module Annotator.Annotation (Annotation(..), fromFile) where
import Data.Char (isSpace) import Data.Char (isSpace)

View file

@ -1,4 +1,4 @@
module Annotator.Util where module Annotator.Util where
anyp :: [a -> Bool] -> a -> Bool anyp :: [a -> Bool] -> a -> Bool
anyp preds x = or (map ($ x) preds) anyp preds x = any ($ x) preds