mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2025-01-05 07:32:36 +00:00
[-Wcalled-once-parameter] Introduce 'called_once' attribute
This commit introduces a new attribute `called_once`. It can be applied to function-like parameters to signify that this parameter should be called exactly once. This concept is particularly widespread in asynchronous programs. Additionally, this commit introduce a new group of dataflow analysis-based warnings to check this property. It identifies and reports the following situations: * parameter is called twice * parameter is never called * parameter is not called on one of the paths Current implementation can also automatically infer `called_once` attribute for completion handler paramaters that should follow the same principle by convention. This behavior is OFF by default and can be turned on by using `-Wcompletion-handler`. Differential Revision: https://reviews.llvm.org/D92039 rdar://72812043
This commit is contained in:
parent
7e4f53f748
commit
fec1a442e3
@ -51,9 +51,7 @@ public:
|
||||
return getParentIgnoreParenCasts(const_cast<Stmt*>(S));
|
||||
}
|
||||
|
||||
bool hasParent(Stmt* S) const {
|
||||
return getParent(S) != nullptr;
|
||||
}
|
||||
bool hasParent(const Stmt *S) const { return getParent(S) != nullptr; }
|
||||
|
||||
bool isConsumedExpr(Expr *E) const;
|
||||
|
||||
|
112
clang/include/clang/Analysis/Analyses/CalledOnceCheck.h
Normal file
112
clang/include/clang/Analysis/Analyses/CalledOnceCheck.h
Normal file
@ -0,0 +1,112 @@
|
||||
//===- CalledOnceCheck.h - Check 'called once' parameters -------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file defines a check for function-like parameters that should be
|
||||
// called exactly one time.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_CALLEDONCECHECK_H
|
||||
#define LLVM_CLANG_ANALYSIS_ANALYSES_CALLEDONCECHECK_H
|
||||
|
||||
namespace clang {
|
||||
|
||||
class AnalysisDeclContext;
|
||||
class CFG;
|
||||
class Decl;
|
||||
class DeclContext;
|
||||
class Expr;
|
||||
class ParmVarDecl;
|
||||
class Stmt;
|
||||
|
||||
/// Classification of situations when parameter is not called on every path.
|
||||
/// \enum IfThen -- then branch of the if statement has no call.
|
||||
/// \enum IfElse -- else branch of the if statement has no call.
|
||||
/// \enum Switch -- one of the switch cases doesn't have a call.
|
||||
/// \enum SwitchSkipped -- there is no call if none of the cases appies.
|
||||
/// \enum LoopEntered -- no call when the loop is entered.
|
||||
/// \enum LoopSkipped -- no call when the loop is not entered.
|
||||
/// \enum FallbackReason -- fallback case when we were not able to figure out
|
||||
/// the reason.
|
||||
enum class NeverCalledReason {
|
||||
IfThen,
|
||||
IfElse,
|
||||
Switch,
|
||||
SwitchSkipped,
|
||||
LoopEntered,
|
||||
LoopSkipped,
|
||||
FallbackReason,
|
||||
LARGEST_VALUE = FallbackReason
|
||||
};
|
||||
|
||||
class CalledOnceCheckHandler {
|
||||
public:
|
||||
CalledOnceCheckHandler() = default;
|
||||
virtual ~CalledOnceCheckHandler() = default;
|
||||
|
||||
/// Called when parameter is called twice.
|
||||
/// \param Parameter -- parameter that should be called once.
|
||||
/// \param Call -- call to report the warning.
|
||||
/// \param PrevCall -- previous call.
|
||||
/// \param IsCompletionHandler -- true, if parameter is a completion handler.
|
||||
/// \param Poised -- true, if the second call is guaranteed to happen after
|
||||
/// the first call.
|
||||
virtual void handleDoubleCall(const ParmVarDecl *Parameter, const Expr *Call,
|
||||
const Expr *PrevCall, bool IsCompletionHandler,
|
||||
bool Poised) {}
|
||||
|
||||
/// Called when parameter is not called at all.
|
||||
/// \param Parameter -- parameter that should be called once.
|
||||
/// \param IsCompletionHandler -- true, if parameter is a completion handler.
|
||||
virtual void handleNeverCalled(const ParmVarDecl *Parameter,
|
||||
bool IsCompletionHandler) {}
|
||||
|
||||
/// Called when captured parameter is not called at all.
|
||||
/// \param Parameter -- parameter that should be called once.
|
||||
/// \param Where -- declaration that captures \p Parameter
|
||||
/// \param IsCompletionHandler -- true, if parameter is a completion handler.
|
||||
virtual void handleCapturedNeverCalled(const ParmVarDecl *Parameter,
|
||||
const Decl *Where,
|
||||
bool IsCompletionHandler) {}
|
||||
|
||||
/// Called when parameter is not called on one of the paths.
|
||||
/// Usually we try to find a statement that is the least common ancestor of
|
||||
/// the path containing the call and not containing the call. This helps us
|
||||
/// to pinpoint a bad path for the user.
|
||||
/// \param Parameter -- parameter that should be called once.
|
||||
/// \param Where -- the least common ancestor statement.
|
||||
/// \param Reason -- a reason describing the path without a call.
|
||||
/// \param IsCalledDirectly -- true, if parameter actually gets called on
|
||||
/// the other path. It is opposed to be used in some other way (added to some
|
||||
/// collection, passed as a parameter, etc.).
|
||||
/// \param IsCompletionHandler -- true, if parameter is a completion handler.
|
||||
virtual void handleNeverCalled(const ParmVarDecl *Parameter,
|
||||
const Stmt *Where, NeverCalledReason Reason,
|
||||
bool IsCalledDirectly,
|
||||
bool IsCompletionHandler) {}
|
||||
};
|
||||
|
||||
/// Check given CFG for 'called once' parameter violations.
|
||||
///
|
||||
/// It traverses the function and tracks how such parameters are used.
|
||||
/// It detects two main violations:
|
||||
/// * parameter is called twice
|
||||
/// * parameter is not called
|
||||
///
|
||||
/// \param AC -- context.
|
||||
/// \param Handler -- a handler for found violations.
|
||||
/// \param CheckConventionalParameters -- true, if we want to check parameters
|
||||
/// not explicitly marked as 'called once', but having the same requirements
|
||||
/// according to conventions.
|
||||
void checkCalledOnceParameters(AnalysisDeclContext &AC,
|
||||
CalledOnceCheckHandler &Handler,
|
||||
bool CheckConventionalParameters);
|
||||
|
||||
} // end namespace clang
|
||||
|
||||
#endif /* LLVM_CLANG_ANALYSIS_ANALYSES_CALLEDONCECHECK_H */
|
@ -1798,6 +1798,13 @@ def ReturnsNonNull : InheritableAttr {
|
||||
let Documentation = [ReturnsNonNullDocs];
|
||||
}
|
||||
|
||||
def CalledOnce : Attr {
|
||||
let Spellings = [Clang<"called_once">];
|
||||
let Subjects = SubjectList<[ParmVar]>;
|
||||
let LangOpts = [ObjC];
|
||||
let Documentation = [CalledOnceDocs];
|
||||
}
|
||||
|
||||
// pass_object_size(N) indicates that the parameter should have
|
||||
// __builtin_object_size with Type=N evaluated on the parameter at the callsite.
|
||||
def PassObjectSize : InheritableParamAttr {
|
||||
|
@ -5162,6 +5162,59 @@ in the future.
|
||||
}];
|
||||
}
|
||||
|
||||
def CalledOnceDocs : Documentation {
|
||||
let Category = DocCatVariable;
|
||||
let Content = [{
|
||||
The ``called_once`` attribute specifies that the annotated function or method
|
||||
parameter is invoked exactly once on all execution paths. It only applies
|
||||
to parameters with function-like types, i.e. function pointers or blocks. This
|
||||
concept is particularly useful for asynchronous programs.
|
||||
|
||||
Clang implements a check for ``called_once`` parameters,
|
||||
``-Wcalled-once-parameter``. It is on by default and finds the following
|
||||
violations:
|
||||
|
||||
* Parameter is not called at all.
|
||||
|
||||
* Parameter is called more than once.
|
||||
|
||||
* Parameter is not called on one of the execution paths.
|
||||
|
||||
In the latter case, Clang pinpoints the path where parameter is not invoked
|
||||
by showing the control-flow statement where the path diverges.
|
||||
|
||||
.. code-block:: objc
|
||||
|
||||
void fooWithCallback(void (^callback)(void) __attribute__((called_once))) {
|
||||
if (somePredicate()) {
|
||||
...
|
||||
callback();
|
||||
} esle {
|
||||
callback(); // OK: callback is called on every path
|
||||
}
|
||||
}
|
||||
|
||||
void barWithCallback(void (^callback)(void) __attribute__((called_once))) {
|
||||
if (somePredicate()) {
|
||||
...
|
||||
callback(); // note: previous call is here
|
||||
}
|
||||
callback(); // warning: callback is called twice
|
||||
}
|
||||
|
||||
void foobarWithCallback(void (^callback)(void) __attribute__((called_once))) {
|
||||
if (somePredicate()) { // warning: callback is not called when condition is false
|
||||
...
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
This attribute is useful for API developers who want to double-check if they
|
||||
implemented their method correctly.
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
def GnuInlineDocs : Documentation {
|
||||
let Category = DocCatFunction;
|
||||
let Content = [{
|
||||
|
@ -487,6 +487,8 @@ def ObjCPointerIntrospect : DiagGroup<"deprecated-objc-pointer-introspection", [
|
||||
def ObjCMultipleMethodNames : DiagGroup<"objc-multiple-method-names">;
|
||||
def ObjCFlexibleArray : DiagGroup<"objc-flexible-array">;
|
||||
def ObjCBoxing : DiagGroup<"objc-boxing">;
|
||||
def CompletionHandler : DiagGroup<"completion-handler">;
|
||||
def CalledOnceParameter : DiagGroup<"called-once-parameter", [CompletionHandler]>;
|
||||
def OpenCLUnsupportedRGBA: DiagGroup<"opencl-unsupported-rgba">;
|
||||
def UnderalignedExceptionObject : DiagGroup<"underaligned-exception-object">;
|
||||
def DeprecatedObjCIsaUsage : DiagGroup<"deprecated-objc-isa-usage">;
|
||||
|
@ -3953,6 +3953,40 @@ def err_attribute_preferred_name_arg_invalid : Error<
|
||||
"argument %0 to 'preferred_name' attribute is not a typedef for "
|
||||
"a specialization of %1">;
|
||||
|
||||
// called-once attribute diagnostics.
|
||||
def err_called_once_attribute_wrong_type : Error<
|
||||
"'called_once' attribute only applies to function-like parameters">;
|
||||
|
||||
def warn_completion_handler_never_called : Warning<
|
||||
"%select{|captured }1completion handler is never called">,
|
||||
InGroup<CompletionHandler>, DefaultIgnore;
|
||||
def warn_called_once_never_called : Warning<
|
||||
"%select{|captured }1%0 parameter marked 'called_once' is never called">,
|
||||
InGroup<CalledOnceParameter>;
|
||||
|
||||
def warn_completion_handler_never_called_when : Warning<
|
||||
"completion handler is never %select{used|called}1 when "
|
||||
"%select{taking true branch|taking false branch|"
|
||||
"handling this case|none of the cases applies|"
|
||||
"entering the loop|skipping the loop|taking one of the branches}2">,
|
||||
InGroup<CompletionHandler>, DefaultIgnore;
|
||||
def warn_called_once_never_called_when : Warning<
|
||||
"%0 parameter marked 'called_once' is never %select{used|called}1 when "
|
||||
"%select{taking true branch|taking false branch|"
|
||||
"handling this case|none of the cases applies|"
|
||||
"entering the loop|skipping the loop|taking one of the branches}2">,
|
||||
InGroup<CalledOnceParameter>;
|
||||
|
||||
def warn_completion_handler_called_twice : Warning<
|
||||
"completion handler is called twice">,
|
||||
InGroup<CompletionHandler>, DefaultIgnore;
|
||||
def warn_called_once_gets_called_twice : Warning<
|
||||
"%0 parameter marked 'called_once' is called twice">,
|
||||
InGroup<CalledOnceParameter>;
|
||||
def note_called_once_gets_called_twice : Note<
|
||||
"previous call is here%select{; set to nil to indicate "
|
||||
"it cannot be called afterwards|}0">;
|
||||
|
||||
// objc_designated_initializer attribute diagnostics.
|
||||
def warn_objc_designated_init_missing_super_call : Warning<
|
||||
"designated initializer missing a 'super' call to a designated initializer of the super class">,
|
||||
|
@ -6,6 +6,7 @@ set(LLVM_LINK_COMPONENTS
|
||||
add_clang_library(clangAnalysis
|
||||
AnalysisDeclContext.cpp
|
||||
BodyFarm.cpp
|
||||
CalledOnceCheck.cpp
|
||||
CFG.cpp
|
||||
CFGReachabilityAnalysis.cpp
|
||||
CFGStmtMap.cpp
|
||||
|
1524
clang/lib/Analysis/CalledOnceCheck.cpp
Normal file
1524
clang/lib/Analysis/CalledOnceCheck.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -24,6 +24,7 @@
|
||||
#include "clang/AST/StmtObjC.h"
|
||||
#include "clang/AST/StmtVisitor.h"
|
||||
#include "clang/Analysis/Analyses/CFGReachabilityAnalysis.h"
|
||||
#include "clang/Analysis/Analyses/CalledOnceCheck.h"
|
||||
#include "clang/Analysis/Analyses/Consumed.h"
|
||||
#include "clang/Analysis/Analyses/ReachableCode.h"
|
||||
#include "clang/Analysis/Analyses/ThreadSafety.h"
|
||||
@ -36,6 +37,7 @@
|
||||
#include "clang/Lex/Preprocessor.h"
|
||||
#include "clang/Sema/ScopeInfo.h"
|
||||
#include "clang/Sema/SemaInternal.h"
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/BitVector.h"
|
||||
#include "llvm/ADT/MapVector.h"
|
||||
#include "llvm/ADT/SmallString.h"
|
||||
@ -1623,6 +1625,82 @@ private:
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class CalledOnceCheckReporter : public CalledOnceCheckHandler {
|
||||
public:
|
||||
CalledOnceCheckReporter(Sema &S) : S(S) {}
|
||||
void handleDoubleCall(const ParmVarDecl *Parameter, const Expr *Call,
|
||||
const Expr *PrevCall, bool IsCompletionHandler,
|
||||
bool Poised) override {
|
||||
auto DiagToReport = IsCompletionHandler
|
||||
? diag::warn_completion_handler_called_twice
|
||||
: diag::warn_called_once_gets_called_twice;
|
||||
S.Diag(Call->getBeginLoc(), DiagToReport) << Parameter;
|
||||
S.Diag(PrevCall->getBeginLoc(), diag::note_called_once_gets_called_twice)
|
||||
<< Poised;
|
||||
}
|
||||
|
||||
void handleNeverCalled(const ParmVarDecl *Parameter,
|
||||
bool IsCompletionHandler) override {
|
||||
auto DiagToReport = IsCompletionHandler
|
||||
? diag::warn_completion_handler_never_called
|
||||
: diag::warn_called_once_never_called;
|
||||
S.Diag(Parameter->getBeginLoc(), DiagToReport)
|
||||
<< Parameter << /* Captured */ false;
|
||||
}
|
||||
|
||||
void handleNeverCalled(const ParmVarDecl *Parameter, const Stmt *Where,
|
||||
NeverCalledReason Reason, bool IsCalledDirectly,
|
||||
bool IsCompletionHandler) override {
|
||||
auto DiagToReport = IsCompletionHandler
|
||||
? diag::warn_completion_handler_never_called_when
|
||||
: diag::warn_called_once_never_called_when;
|
||||
S.Diag(Where->getBeginLoc(), DiagToReport)
|
||||
<< Parameter << IsCalledDirectly << (unsigned)Reason;
|
||||
}
|
||||
|
||||
void handleCapturedNeverCalled(const ParmVarDecl *Parameter,
|
||||
const Decl *Where,
|
||||
bool IsCompletionHandler) override {
|
||||
auto DiagToReport = IsCompletionHandler
|
||||
? diag::warn_completion_handler_never_called
|
||||
: diag::warn_called_once_never_called;
|
||||
S.Diag(Where->getBeginLoc(), DiagToReport)
|
||||
<< Parameter << /* Captured */ true;
|
||||
}
|
||||
|
||||
private:
|
||||
Sema &S;
|
||||
};
|
||||
|
||||
constexpr unsigned CalledOnceWarnings[] = {
|
||||
diag::warn_called_once_never_called,
|
||||
diag::warn_called_once_never_called_when,
|
||||
diag::warn_called_once_gets_called_twice};
|
||||
|
||||
constexpr unsigned CompletionHandlerWarnings[]{
|
||||
diag::warn_completion_handler_never_called,
|
||||
diag::warn_completion_handler_never_called_when,
|
||||
diag::warn_completion_handler_called_twice};
|
||||
|
||||
bool shouldAnalyzeCalledOnceImpl(llvm::ArrayRef<unsigned> DiagIDs,
|
||||
const DiagnosticsEngine &Diags,
|
||||
SourceLocation At) {
|
||||
return llvm::any_of(DiagIDs, [&Diags, At](unsigned DiagID) {
|
||||
return !Diags.isIgnored(DiagID, At);
|
||||
});
|
||||
}
|
||||
|
||||
bool shouldAnalyzeCalledOnceConventions(const DiagnosticsEngine &Diags,
|
||||
SourceLocation At) {
|
||||
return shouldAnalyzeCalledOnceImpl(CompletionHandlerWarnings, Diags, At);
|
||||
}
|
||||
|
||||
bool shouldAnalyzeCalledOnceParameters(const DiagnosticsEngine &Diags,
|
||||
SourceLocation At) {
|
||||
return shouldAnalyzeCalledOnceImpl(CalledOnceWarnings, Diags, At) ||
|
||||
shouldAnalyzeCalledOnceConventions(Diags, At);
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
namespace clang {
|
||||
@ -2264,6 +2342,17 @@ AnalysisBasedWarnings::IssueWarnings(sema::AnalysisBasedWarnings::Policy P,
|
||||
}
|
||||
}
|
||||
|
||||
// Check for violations of "called once" parameter properties.
|
||||
if (S.getLangOpts().ObjC &&
|
||||
shouldAnalyzeCalledOnceParameters(Diags, D->getBeginLoc())) {
|
||||
if (AC.getCFG()) {
|
||||
CalledOnceCheckReporter Reporter(S);
|
||||
checkCalledOnceParameters(
|
||||
AC, Reporter,
|
||||
shouldAnalyzeCalledOnceConventions(Diags, D->getBeginLoc()));
|
||||
}
|
||||
}
|
||||
|
||||
bool FallThroughDiagFull =
|
||||
!Diags.isIgnored(diag::warn_unannotated_fallthrough, D->getBeginLoc());
|
||||
bool FallThroughDiagPerFunction = !Diags.isIgnored(
|
||||
|
@ -3693,6 +3693,26 @@ static void handleCallbackAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
|
||||
S.Context, AL, EncodingIndices.data(), EncodingIndices.size()));
|
||||
}
|
||||
|
||||
static bool isFunctionLike(const Type &T) {
|
||||
// Check for explicit function types.
|
||||
// 'called_once' is only supported in Objective-C and it has
|
||||
// function pointers and block pointers.
|
||||
return T.isFunctionPointerType() || T.isBlockPointerType();
|
||||
}
|
||||
|
||||
/// Handle 'called_once' attribute.
|
||||
static void handleCalledOnceAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
|
||||
// 'called_once' only applies to parameters representing functions.
|
||||
QualType T = cast<ParmVarDecl>(D)->getType();
|
||||
|
||||
if (!isFunctionLike(*T)) {
|
||||
S.Diag(AL.getLoc(), diag::err_called_once_attribute_wrong_type);
|
||||
return;
|
||||
}
|
||||
|
||||
D->addAttr(::new (S.Context) CalledOnceAttr(S.Context, AL));
|
||||
}
|
||||
|
||||
static void handleTransparentUnionAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
|
||||
// Try to find the underlying union declaration.
|
||||
RecordDecl *RD = nullptr;
|
||||
@ -7692,6 +7712,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D,
|
||||
case ParsedAttr::AT_Callback:
|
||||
handleCallbackAttr(S, D, AL);
|
||||
break;
|
||||
case ParsedAttr::AT_CalledOnce:
|
||||
handleCalledOnceAttr(S, D, AL);
|
||||
break;
|
||||
case ParsedAttr::AT_CUDAGlobal:
|
||||
handleGlobalAttr(S, D, AL);
|
||||
break;
|
||||
|
@ -15404,10 +15404,6 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc,
|
||||
|
||||
PopDeclContext();
|
||||
|
||||
// Pop the block scope now but keep it alive to the end of this function.
|
||||
AnalysisBasedWarnings::Policy WP = AnalysisWarnings.getDefaultPolicy();
|
||||
PoppedFunctionScopePtr ScopeRAII = PopFunctionScopeInfo(&WP, BD, BlockTy);
|
||||
|
||||
// Set the captured variables on the block.
|
||||
SmallVector<BlockDecl::Capture, 4> Captures;
|
||||
for (Capture &Cap : BSI->Captures) {
|
||||
@ -15475,6 +15471,10 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc,
|
||||
}
|
||||
BD->setCaptures(Context, Captures, BSI->CXXThisCaptureIndex != 0);
|
||||
|
||||
// Pop the block scope now but keep it alive to the end of this function.
|
||||
AnalysisBasedWarnings::Policy WP = AnalysisWarnings.getDefaultPolicy();
|
||||
PoppedFunctionScopePtr ScopeRAII = PopFunctionScopeInfo(&WP, BD, BlockTy);
|
||||
|
||||
BlockExpr *Result = new (Context) BlockExpr(BD, BlockTy);
|
||||
|
||||
// If the block isn't obviously global, i.e. it captures anything at
|
||||
|
@ -40,6 +40,7 @@
|
||||
// CHECK-NEXT: CXX11NoReturn (SubjectMatchRule_function)
|
||||
// CHECK-NEXT: CallableWhen (SubjectMatchRule_function_is_member)
|
||||
// CHECK-NEXT: Callback (SubjectMatchRule_function)
|
||||
// CHECK-NEXT: CalledOnce (SubjectMatchRule_variable_is_parameter)
|
||||
// CHECK-NEXT: Capability (SubjectMatchRule_record, SubjectMatchRule_type_alias)
|
||||
// CHECK-NEXT: CarriesDependency (SubjectMatchRule_variable_is_parameter, SubjectMatchRule_objc_method, SubjectMatchRule_function)
|
||||
// CHECK-NEXT: Cleanup (SubjectMatchRule_variable_is_local)
|
||||
|
20
clang/test/SemaObjC/attr-called-once.m
Normal file
20
clang/test/SemaObjC/attr-called-once.m
Normal file
@ -0,0 +1,20 @@
|
||||
// RUN: %clang_cc1 -verify -fsyntax-only -fobjc-arc -fblocks %s
|
||||
|
||||
#define CALLED_ONCE __attribute__((called_once))
|
||||
|
||||
void test1(int x CALLED_ONCE); // expected-error{{'called_once' attribute only applies to function-like parameters}}
|
||||
void test2(double x CALLED_ONCE); // expected-error{{'called_once' attribute only applies to function-like parameters}}
|
||||
|
||||
void test3(void (*foo)() CALLED_ONCE); // no-error
|
||||
void test4(int (^foo)(int) CALLED_ONCE); // no-error
|
||||
|
||||
void test5(void (*foo)() __attribute__((called_once(1))));
|
||||
// expected-error@-1{{'called_once' attribute takes no arguments}}
|
||||
void test6(void (*foo)() __attribute__((called_once("str1", "str2"))));
|
||||
// expected-error@-1{{'called_once' attribute takes no arguments}}
|
||||
|
||||
CALLED_ONCE void test7(); // expected-warning{{'called_once' attribute only applies to parameters}}
|
||||
void test8() {
|
||||
void (*foo)() CALLED_ONCE; // expected-warning{{'called_once' attribute only applies to parameters}}
|
||||
foo();
|
||||
}
|
1050
clang/test/SemaObjC/warn-called-once.m
Normal file
1050
clang/test/SemaObjC/warn-called-once.m
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user