summaryrefslogtreecommitdiff
path: root/compilerplugins/clang/sallogareas.cxx
blob: 512177d4cdb2a672d13b71fd27d4bff1619dbeb5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * Based on LLVM/Clang.
 *
 * This file is distributed under the University of Illinois Open Source
 * License. See LICENSE.TXT for details.
 *
 */

#include "sallogareas.hxx"

#include <clang/Lex/Lexer.h>

#include <fstream>

namespace loplugin
{

/*
This is a compile check.

Check area used in SAL_INFO/SAL_WARN macros against the list in include/sal/log-areas.dox and
report if the area is not listed there. The fix is either use a proper area or add it to the list
if appropriate.
*/

SalLogAreas::SalLogAreas( const InstantiationData& data )
    : Plugin(data), inFunction(nullptr)
    {
    }

void SalLogAreas::run()
    {
    inFunction = NULL;
    lastSalDetailLogStreamMacro = SourceLocation();
    TraverseDecl( compiler.getASTContext().getTranslationUnitDecl());
    }

bool SalLogAreas::VisitFunctionDecl( const FunctionDecl* function )
    {
    inFunction = function;
    return true;
    }

bool SalLogAreas::VisitCallExpr( const CallExpr* call )
    {
    if( ignoreLocation( call ))
        return true;
    if( const FunctionDecl* func = call->getDirectCallee())
        {
        // Optimize, getQualifiedNameAsString() is reportedly expensive.
        if( func->getNumParams() == 4 && func->getIdentifier() != NULL
            && ( func->getName() == "sal_detail_log" || func->getName() == "log" ))
            {
            string qualifiedName = func->getQualifiedNameAsString();
            if( qualifiedName == "sal_detail_log" || qualifiedName == "sal::detail::log" )
                {
                // The SAL_DETAIL_LOG_STREAM macro expands to two calls to sal::detail::log(),
                // so do not warn repeatedly about the same macro (the area->getLocStart() of all the calls
                // from the same macro should be the same).
                SourceLocation expansionLocation = compiler.getSourceManager().getExpansionLoc( call->getLocStart());
                if( expansionLocation == lastSalDetailLogStreamMacro )
                    return true;
                lastSalDetailLogStreamMacro = expansionLocation;
                if( const StringLiteral* area = dyn_cast< StringLiteral >( call->getArg( 1 )->IgnoreParenImpCasts()))
                    {
                    if( area->getKind() == StringLiteral::Ascii )
                        checkArea( area->getBytes(), area->getExprLoc());
                    else
                        report( DiagnosticsEngine::Warning, "unsupported string literal kind (plugin needs fixing?)",
                            area->getLocStart());
                    return true;
                    }
                if( inFunction->getQualifiedNameAsString() == "sal::detail::log" )
                    return true; // This function only forwards to sal_detail_log, so ok.
                if( call->getArg( 1 )->isNullPointerConstant( compiler.getASTContext(),
                    Expr::NPC_ValueDependentIsNotNull ) != Expr::NPCK_NotNull )
                    { // If the area argument is a null pointer, that is allowed only for SAL_DEBUG.
                    const SourceManager& source = compiler.getSourceManager();
                    for( SourceLocation loc = call->getLocStart();
                         loc.isMacroID();
                         loc = source.getImmediateExpansionRange( loc ).first )
                        {
                        StringRef inMacro = Lexer::getImmediateMacroName( loc, source, compiler.getLangOpts());
                        if( inMacro == "SAL_DEBUG" )
                            return true; // ok
                        }
                    report( DiagnosticsEngine::Warning, "missing log area",
                        call->getArg( 1 )->IgnoreParenImpCasts()->getLocStart());
                    return true;
                    }
                report( DiagnosticsEngine::Warning, "cannot analyse log area argument (plugin needs fixing?)",
                    call->getLocStart());
                }
            }
        }
    return true;
    }

void SalLogAreas::checkArea( StringRef area, SourceLocation location )
    {
    if( logAreas.empty())
        readLogAreas();
    if( !logAreas.count( area ))
        {
        report( DiagnosticsEngine::Warning, "unknown log area '%0' (check or extend include/sal/log-areas.dox)",
            location ) << area;
        checkAreaSyntax(area, location);
        }
    }

void SalLogAreas::checkAreaSyntax(StringRef area, SourceLocation location) {
    for (std::size_t i = 0;;) {
        std::size_t j = area.find('.', i);
        if (j == StringRef::npos) {
            j = area.size();
        }
        if (j == i) {
            goto bad;
        }
        for (; i != j; ++i) {
            auto c = area[i];
            if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z'))) {
                goto bad;
            }
        }
        if (j == area.size()) {
            return;
        }
        i = j + 1;
    }
bad:
    report(
        DiagnosticsEngine::Warning,
        "invalid log area syntax '%0'%1 (see include/sal/log.hxx for details)",
        location)
        << area << (location.isValid() ? "" : " in include/sal/log-areas.dox");
}

void SalLogAreas::readLogAreas()
    {
    ifstream is( SRCDIR "/include/sal/log-areas.dox" );
    while( is.good())
        {
        string line;
        getline( is, line );
        size_t pos = line.find( "@li @c " );
        if( pos != string::npos )
            {
            pos += strlen( "@li @c " );
            size_t end = line.find( ' ', pos );
            std::string area;
            if( end == string::npos )
                area = line.substr( pos );
            else if( pos != end )
                area = line.substr( pos, end - pos );
            checkAreaSyntax(area, SourceLocation());
            logAreas.insert(area);
            }
        }
    // If you get this error message, you possibly have too old icecream (ICECC_EXTRAFILES is needed).
    if( logAreas.empty())
        report( DiagnosticsEngine::Warning, "error reading log areas" );
    }

static Plugin::Registration< SalLogAreas > X( "sallogareas" );

} // namespace

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */