/* -*- Mode: C++; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * Tartan * Copyright © 2013 Collabora Ltd. * * Tartan is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Tartan is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Tartan. If not, see . * * Authors: * Philip Withnall */ /** * NullabilityVisitor: * * This AST visitor inspects function declarations, checking the nullability of * their parameters is correctly annotated. It checks for the presence of: * • A nonnull attribute on the function. * • (nullable) annotations on the parameters. * • (optional) annotations on the parameters. * • g_return[_val]_if_fail() precondition assertions in the function body. * * It then checks that the assertions implied by these three sources agree, and * that a reasonable number of the sources exist. If two sources conflict (e.g. * a parameter has an (nullable) or (optional) attribute but also has a * g_return_if_fail(param != NULL) precondition assertion), an error is emitted. * Otherwise, if the nullability of a parameter is uncertain, warnings are * emitted to ask the user to add a precondition assertion or a (nullable) or * (optional) annotation. The user is only optionally requested to add nonnull * attributes, as many users have voiced opinions that such attributes are ugly. * * Note that precondition assertions can only be used for non-NULL checks by the * static analyser within a single code base. For analysis across code bases, * (nullable) and (optional) annotations or nonnull attributes are needed. Hence * the warnings emitted by this checker try to gently push the user towards * adding them. * * FIXME: The majority of false positives from this checker come from implicit * nonnull attributes added to functions which aren’t correctly annotated with * (nullable) or (optional). The checker produces an error like the following: * Missing non-NULL precondition assertion on the ‘key’ parameter of * function g_hash_table_remove() (already has a nonnull attribute). * Which implies that a nonnull attribute exists when it doesn’t. Instead, the * error should say that a non-NULL precondition assertion *or* a (nullable) * annotation should be added, and it should ignore the implicit nonnull * attribute. However, if a function has an explicit nonnull attribute (i.e. not * implicitly added by the analyser plugin) the warning message should be * unchanged. */ #include #include #include #include #include "assertion-extracter.h" #include "debug.h" #include "nullability-checker.h" namespace tartan { void NullabilityConsumer::HandleTranslationUnit (ASTContext& context) { /* Run away if the plugin is disabled. */ if (!this->is_enabled ()) { return; } this->_visitor.TraverseDecl (context.getTranslationUnitDecl ()); } /* Note: Specifically overriding the Traverse* method here to re-implement * recursion to child nodes. */ bool NullabilityVisitor::TraverseFunctionDecl (FunctionDecl* func) { /* Ignore static functions immediately; they shouldn’t have any * GIR data, and searching for it massively slows down * compilation. */ StorageClass sc = func->getStorageClass (); if (sc != SC_None && sc != SC_Extern) return true; /* Can only handle functions which have a body defined. */ Stmt* func_body = func->getBody (); if (func_body == NULL || !func->isThisDeclarationADefinition ()) return true; /* The body should be a compound statement, e.g. * { stmt; stmt; } */ CompoundStmt* body_stmt = dyn_cast (func_body); if (body_stmt == NULL) { DEBUG ("Ignoring function " << func->getNameAsString () << " due to having a non-compound statement body."); return true; } DEBUG ("Examining " << func->getNameAsString ()); /* For each parameter, check whether it has a (nullable) annotation, * a nonnull attribute, and a non-NULL assertion. */ NonNullAttr* nonnull_attr = func->getAttr (); if (nonnull_attr != NULL) { DEBUG ("nonnull attribute indices:"); for (NonNullAttr::args_iterator it = nonnull_attr->args_begin (), ie = nonnull_attr->args_end (); it != ie; ++it) { DEBUG ("\t" << it->getSourceIndex ()); } } else { DEBUG ("No nonnull attribute."); } /* Try to find typelib information about the function. */ std::string func_name = func->getNameAsString (); /* TODO: expensive? */ GIBaseInfo* info = this->_gir_manager.get ()->find_function_info (func_name); if (info == NULL) return true; if (g_base_info_get_type (info) != GI_INFO_TYPE_FUNCTION) { WARN ("Error: Unhandled GI type " << g_base_info_get_type (info) << " in introspection info " "for function ‘" << func_name << "’."); return true; } /* Parse the function’s body for assertions. */ std::unordered_set asserted_parms; ASTContext& context = func->getASTContext (); /* Iterate through the function body until the first non-assertion and * non-declaration statement is reached. Specifically stop before the * first assignment, as that could affect the outcome of any subsequent * assertions. */ for (CompoundStmt::const_body_iterator it = body_stmt->body_begin (), ie = body_stmt->body_end (); it != ie; ++it) { Stmt* st = *it; Expr* assertion_expr = AssertionExtracter::is_assertion_stmt (*st, context); if (assertion_expr == NULL) { /* Potential program state mutation reached, so run * away. */ break; } /* If the assertion is a non-NULL check, record the parameters * it checks. */ AssertionExtracter::assertion_is_nonnull_check (*assertion_expr, context, asserted_parms); } DEBUG (""); GICallableInfo *callable_info = (GICallableInfo *) info; /* Handle the parameters. */ for (FunctionDecl::param_const_iterator it = func->param_begin (), ie = func->param_end (); it != ie; ++it) { ParmVarDecl* parm_decl = *it; unsigned int idx = parm_decl->getFunctionScopeIndex (); GIArgInfo arg; GITypeInfo type_info; /* Skip non-pointer arguments. */ if (!parm_decl->getType ()->isPointerType ()) continue; g_callable_info_load_arg (callable_info, idx, &arg); g_arg_info_load_type (&arg, &type_info); enum { EXPLICIT_NULLABLE, /* 0 */ MAYBE, /* ? */ EXPLICIT_NONNULL, /* 1 */ } has_nonnull = (nonnull_attr == NULL) ? MAYBE : (nonnull_attr->isNonNull (idx)) ? EXPLICIT_NONNULL: EXPLICIT_NULLABLE; bool has_nullable = (g_arg_info_may_be_null (&arg) || g_arg_info_is_optional (&arg)); bool has_assertion = (asserted_parms.count (parm_decl) > 0); /* Analysis: * * nonnull | allow-none | assertion | Outcome * --------+------------+-----------+------------- * 0 | 0 | 0 | Warning * 0 | 0 | 1 | Warning * 0 | 1 | 0 | Perfect * 0 | 1 | 1 | Error * ? | 0 | 0 | Warning * ? | 0 | 1 | Soft warning * ? | 1 | 0 | Perfect * ? | 1 | 1 | Error * 1 | 0 | 0 | Warning * 1 | 0 | 1 | Perfect * 1 | 1 | 0 | Error * 1 | 1 | 1 | Error * * Which gives the following rules: * nonnull ∧ allow-none ⇒ Error (conflict) * allow-none ∧ assertion ⇒ Error (conflict) * ¬allow-none ∧ ¬assertion ⇒ Warning (need one or other) * ¬nonnull ∧ assertion ⇒ Warning (incomplete nonnull) * (nonnull = ?) ∧ assertion ⇒ * Soft warning (missing nonnull) * * FIXME: This analysis currently considers (nullable) and * (optional) annotations as equivalent. It should be expanded * to consider them separately, along with the type of the * variable in question, since (nullable) will not affect the * nullability of an out function parameter. */ if (has_nonnull == EXPLICIT_NONNULL && has_nullable) { Debug::emit_error ( "Conflict between nonnull attribute and " "(nullable), (optional) or (allow-none) " "annotation on the ‘%0’ parameter " "of function %1().", this->_compiler, #ifdef HAVE_LLVM_8_0 parm_decl->getBeginLoc () #else parm_decl->getLocStart () #endif ) << parm_decl->getNameAsString () << func->getNameAsString (); } else if (has_nullable && has_assertion) { Debug::emit_error ( "Conflict between (nullable), (optional) or " "(allow-none) annotation and " "non-NULL precondition assertion on the ‘%0’ " "parameter of function %1().", this->_compiler, #ifdef HAVE_LLVM_8_0 parm_decl->getBeginLoc () #else parm_decl->getLocStart () #endif ) << parm_decl->getNameAsString () << func->getNameAsString (); } else if (!has_nullable && !has_assertion) { switch (has_nonnull) { case EXPLICIT_NULLABLE: Debug::emit_warning ( "Missing (nullable) or (optional) " "annotation on " "the ‘%0’ parameter of function %1() " "(already has a nonnull attribute or " "no non-NULL precondition assertion).", this->_compiler, #ifdef HAVE_LLVM_8_0 parm_decl->getBeginLoc () #else parm_decl->getLocStart () #endif ) << parm_decl->getNameAsString () << func->getNameAsString (); break; case MAYBE: Debug::emit_warning ( "Missing (nullable) or (optional) " "annotation or " "non-NULL precondition assertion on " "the ‘%0’ parameter of function %1().", this->_compiler, #ifdef HAVE_LLVM_8_0 parm_decl->getBeginLoc () #else parm_decl->getLocStart () #endif ) << parm_decl->getNameAsString () << func->getNameAsString (); break; case EXPLICIT_NONNULL: Debug::emit_warning ( "Missing non-NULL precondition " "assertion on the ‘%0’ parameter of " "function %1() (already has a nonnull " "attribute or no (nullable), " "(optional) or (allow-none) " "annotation).", this->_compiler, #ifdef HAVE_LLVM_8_0 parm_decl->getBeginLoc () #else parm_decl->getLocStart () #endif ) << parm_decl->getNameAsString () << func->getNameAsString (); break; default: g_assert_not_reached (); } } else if (has_nonnull == EXPLICIT_NULLABLE && has_assertion) { Debug::emit_warning ( "Conflict between nonnull attribute and " "non-NULL precondition annotation on the ‘%0’ " "parameter of function %1().", this->_compiler, #ifdef HAVE_LLVM_8_0 parm_decl->getBeginLoc () #else parm_decl->getLocStart () #endif ) << parm_decl->getNameAsString () << func->getNameAsString (); } else if (has_nonnull == MAYBE && has_assertion) { /* TODO: Make this a soft warning (disabled by default) * if it comes up with too many false positives. */ Debug::emit_warning ( "Missing nonnull attribute for the ‘%0’ " "parameter of function %1() (already has a " "non-NULL precondition assertion).", this->_compiler, #ifdef HAVE_LLVM_8_0 parm_decl->getBeginLoc () #else parm_decl->getLocStart () #endif ) << parm_decl->getNameAsString () << func->getNameAsString (); } } g_base_info_unref (info); return true; } } /* namespace tartan */