Skip to content

Commit 2a80a9d

Browse files
authored
[-Wunsafe-buffer-usage] Move warning-only analysis back to function-based (llvm#198006)
Move the warning-only analysis back to the end of parsing each Decl. The warning-only analysis no longer does any extra AST deserialization. Pre-compiled code will only be analyzed once during its own compilation. When `-fsafe-buffer-usage-suggestions` is used, the behavior is the same as before, because it requires visibility of the whole translation unit. rdar://177185295 Also fix rdar://107480207 & rdar://176992568 for the warning-only case.
1 parent 09709d7 commit 2a80a9d

9 files changed

Lines changed: 254 additions & 115 deletions

clang/docs/ReleaseNotes.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,20 @@ Attribute Changes in Clang
410410
- Name: myUnsafeFunction
411411
UnsafeBufferUsage: true
412412
413+
- When using ``-Wunsafe-buffer-usage`` without
414+
``-fsafe-buffer-usage-suggestions``, warnings are now emitted only
415+
once per source file. Pre-compiled code (such as PCH or module
416+
headers) is no longer repeatedly analyzed, as it is analyzed during
417+
its initial compilation. (Traditionally included headers are still
418+
analyzed within each translation unit that includes them). This
419+
behavior matches most of other ``-W`` diagnostics.
420+
421+
When ``-fsafe-buffer-usage-suggestions`` is enabled, the behavior
422+
remains the same as before: pre-compiled code is deserialized and
423+
analyzed alongside the translation unit that uses it, because fix-it
424+
suggestion analysis requires full visibility of the translation
425+
unit.
426+
413427
- Added support for ``[[msvc::forceinline]]`` for functions and
414428
``[[msvc::forceinline_calls]]`` for statements. Both are aliases to
415429
``[[clang::always_inline]]`` with additional checks to ensure that they

clang/lib/Sema/AnalysisBasedWarnings.cpp

Lines changed: 69 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2933,6 +2933,32 @@ LifetimeSafetyTUAnalysis(Sema &S, TranslationUnitDecl *TU,
29332933
}
29342934
}
29352935

2936+
static bool shouldRunUnsafeBufferUsageAnalysis(const Sema &S,
2937+
SourceLocation Loc) {
2938+
const DiagnosticsEngine &Diags = S.getDiagnostics();
2939+
return !Diags.isIgnored(diag::warn_unsafe_buffer_operation, Loc) ||
2940+
!Diags.isIgnored(diag::warn_unsafe_buffer_variable, Loc) ||
2941+
!Diags.isIgnored(diag::warn_unsafe_buffer_usage_in_container, Loc) ||
2942+
(!Diags.isIgnored(diag::warn_unsafe_buffer_libc_call, Loc) &&
2943+
S.getLangOpts().CPlusPlus);
2944+
}
2945+
2946+
/// \return true iff fix-its should be emitted along with -Wunsafe-buffer-usage
2947+
/// warnings
2948+
static bool shouldEmitUnsafeBufferUsageSuggestions(const Sema &S) {
2949+
return S.getLangOpts().CPlusPlus20 && S.getDiagnostics()
2950+
.getDiagnosticOptions()
2951+
.ShowSafeBufferUsageSuggestions;
2952+
}
2953+
2954+
/// \return true iff an extra note that encourages users to turn on fix-its
2955+
/// should be emitted along with -Wunsafe-buffer-usage warnings
2956+
static bool shouldSuggestUnsafeBufferUsageSuggestions(const Sema &S) {
2957+
return S.getLangOpts().CPlusPlus20 && !S.getDiagnostics()
2958+
.getDiagnosticOptions()
2959+
.ShowSafeBufferUsageSuggestions;
2960+
}
2961+
29362962
void clang::sema::AnalysisBasedWarnings::IssueWarnings(
29372963
TranslationUnitDecl *TU) {
29382964
if (!TU)
@@ -2944,48 +2970,33 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
29442970
// exit if having uncompilable errors or ignoring all warnings:
29452971
return;
29462972

2947-
DiagnosticOptions &DiagOpts = Diags.getDiagnosticOptions();
2948-
2949-
// UnsafeBufferUsage analysis settings.
2950-
bool UnsafeBufferUsageCanEmitSuggestions = S.getLangOpts().CPlusPlus20;
2951-
bool UnsafeBufferUsageShouldEmitSuggestions = // Should != Can.
2952-
UnsafeBufferUsageCanEmitSuggestions &&
2953-
DiagOpts.ShowSafeBufferUsageSuggestions;
2954-
bool UnsafeBufferUsageShouldSuggestSuggestions =
2955-
UnsafeBufferUsageCanEmitSuggestions &&
2956-
!DiagOpts.ShowSafeBufferUsageSuggestions;
2957-
UnsafeBufferUsageReporter R(S, UnsafeBufferUsageShouldSuggestSuggestions);
2958-
2959-
// The Callback function that performs analyses:
2960-
auto CallAnalyzers = [&](const Decl *Node) -> void {
2961-
if (Node->hasAttr<UnsafeBufferUsageAttr>())
2962-
return;
2973+
// When the '-fsafe-buffer-usage-suggestions' option is enabled,
2974+
// the '-Wunsafe-buffer-usage' analysis is performed at the end of the
2975+
// translation unit. Otherwise, the analysis is more efficiently performed at
2976+
// the end of each Decl during parsing.
2977+
if (shouldEmitUnsafeBufferUsageSuggestions(S)) {
2978+
UnsafeBufferUsageReporter R(S, /*SuggestSuggestions=*/false);
29632979

2964-
// Perform unsafe buffer usage analysis:
2965-
if (!Diags.isIgnored(diag::warn_unsafe_buffer_operation,
2966-
Node->getBeginLoc()) ||
2967-
!Diags.isIgnored(diag::warn_unsafe_buffer_variable,
2968-
Node->getBeginLoc()) ||
2969-
!Diags.isIgnored(diag::warn_unsafe_buffer_usage_in_container,
2970-
Node->getBeginLoc()) ||
2971-
!Diags.isIgnored(diag::warn_unsafe_buffer_libc_call,
2972-
Node->getBeginLoc())) {
2973-
clang::checkUnsafeBufferUsage(Node, R,
2974-
UnsafeBufferUsageShouldEmitSuggestions);
2975-
}
2976-
2977-
// More analysis ...
2978-
};
2979-
// Emit per-function analysis-based warnings that require the whole-TU
2980-
// reasoning. Check if any of them is enabled at all before scanning the AST:
2981-
if (!Diags.isIgnored(diag::warn_unsafe_buffer_operation, SourceLocation()) ||
2982-
!Diags.isIgnored(diag::warn_unsafe_buffer_variable, SourceLocation()) ||
2983-
!Diags.isIgnored(diag::warn_unsafe_buffer_usage_in_container,
2984-
SourceLocation()) ||
2985-
(!Diags.isIgnored(diag::warn_unsafe_buffer_libc_call, SourceLocation()) &&
2986-
S.getLangOpts().CPlusPlus /* only warn about libc calls in C++ */)) {
2987-
CallableVisitor(CallAnalyzers, TU->getOwningModule())
2988-
.TraverseTranslationUnitDecl(TU);
2980+
// The Callback function that performs analyses:
2981+
auto CallAnalyzers = [&](const Decl *Node) -> void {
2982+
if (Node->hasAttr<UnsafeBufferUsageAttr>())
2983+
return;
2984+
2985+
// Perform unsafe buffer usage analysis:
2986+
if (shouldRunUnsafeBufferUsageAnalysis(S, Node->getBeginLoc())) {
2987+
clang::checkUnsafeBufferUsage(Node, R,
2988+
/*EmitSuggestion =*/true);
2989+
}
2990+
2991+
// More analysis ...
2992+
};
2993+
// Emit per-function analysis-based warnings that require the whole-TU
2994+
// reasoning. Check if any of them is enabled at all before scanning the
2995+
// AST:
2996+
if (shouldRunUnsafeBufferUsageAnalysis(S, SourceLocation())) {
2997+
CallableVisitor(CallAnalyzers, TU->getOwningModule())
2998+
.TraverseTranslationUnitDecl(TU);
2999+
}
29893000
}
29903001

29913002
if (S.getLangOpts().CPlusPlus &&
@@ -3221,6 +3232,23 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
32213232
++NumFunctionsWithBadCFGs;
32223233
}
32233234
}
3235+
3236+
// If the '-fsafe-buffer-usage-suggestions' option is not specified or C++20
3237+
// is not available, '-Wunsafe-buffer-usage' warnings are analyzed at the end
3238+
// of each Decl. This is because only '-fsafe-buffer-usage-suggestions'
3239+
// requires visibility of the whole translation unit, hence the cumbersome
3240+
// post-TU analysis, which deserializes and scans pre-compiled ASTs.
3241+
if (!shouldEmitUnsafeBufferUsageSuggestions(S) &&
3242+
!D->hasAttr<UnsafeBufferUsageAttr>()) {
3243+
UnsafeBufferUsageReporter R(S,
3244+
shouldSuggestUnsafeBufferUsageSuggestions(S));
3245+
3246+
// Perform unsafe buffer usage analysis:
3247+
if (shouldRunUnsafeBufferUsageAnalysis(S, D->getBeginLoc())) {
3248+
clang::checkUnsafeBufferUsage(
3249+
D, R, /*UnsafeBufferUsageShouldEmitSuggestions=*/false);
3250+
}
3251+
}
32243252
}
32253253

32263254
void clang::sema::AnalysisBasedWarnings::PrintStats() const {

clang/test/Modules/safe_buffers_optout.cpp

Lines changed: 39 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,37 @@
22
// RUN: mkdir -p %t
33
// RUN: split-file %s %t
44

5-
// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -emit-module -fmodule-name=safe_buffers_test_base -x c++ %t/safe_buffers_test.modulemap -std=c++20\
6-
// RUN: -o %t/safe_buffers_test_base.pcm -Wunsafe-buffer-usage
7-
// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -emit-module -fmodule-name=safe_buffers_test_textual -x c++ %t/safe_buffers_test.modulemap -std=c++20\
8-
// RUN: -o %t/safe_buffers_test_textual.pcm -Wunsafe-buffer-usage
9-
// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -emit-module -fmodule-name=safe_buffers_test_optout -x c++ %t/safe_buffers_test.modulemap -std=c++20\
10-
// RUN: -fmodule-file=%t/safe_buffers_test_base.pcm -fmodule-file=%t/safe_buffers_test_textual.pcm \
11-
// RUN: -o %t/safe_buffers_test_optout.pcm -Wunsafe-buffer-usage
12-
// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodule-file=%t/safe_buffers_test_optout.pcm -I %t -std=c++20 -Wunsafe-buffer-usage\
13-
// RUN: -verify %t/safe_buffers_optout-explicit.cpp
14-
15-
16-
// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -verify -fmodules-cache-path=%t -fmodule-map-file=%t/safe_buffers_test.modulemap -I%t\
17-
// RUN: -x c++ -std=c++20 -Wunsafe-buffer-usage %t/safe_buffers_optout-implicit.cpp
185

6+
// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -emit-module -fmodule-name=safe_buffers_test_base -x c++\
7+
// RUN: %t/safe_buffers_test.modulemap -std=c++20 -o %t/safe_buffers_test_base.pcm -Wunsafe-buffer-usage -verify
198
//--- safe_buffers_test.modulemap
209
module safe_buffers_test_base {
2110
header "base.h"
2211
}
2312

13+
// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -emit-module -fmodule-name=safe_buffers_test_textual -x c++ \
14+
// RUN: %t/safe_buffers_test.modulemap -std=c++20 -o %t/safe_buffers_test_textual.pcm \
15+
// RUN: -Wunsafe-buffer-usage
2416
module safe_buffers_test_textual {
2517
textual header "textual.h"
2618
}
2719

20+
// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -emit-module -fmodule-name=safe_buffers_test_optout -x c++ \
21+
// RUN: %t/safe_buffers_test.modulemap -std=c++20 -fmodule-file=%t/safe_buffers_test_base.pcm \
22+
// RUN: -fmodule-file=%t/safe_buffers_test_textual.pcm -o %t/safe_buffers_test_optout.pcm -Wunsafe-buffer-usage\
23+
// RUN: -verify
2824
module safe_buffers_test_optout {
2925
explicit module test_sub1 { header "test_sub1.h" }
3026
explicit module test_sub2 { header "test_sub2.h" }
3127
use safe_buffers_test_base
3228
}
3329

30+
3431
//--- base.h
3532
#ifdef __cplusplus
3633
int base(int *p) {
37-
int x = p[5];
34+
int x = p[5]; // expected-warning{{unsafe buffer access}}\
35+
expected-note{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}}
3836
#pragma clang unsafe_buffer_usage begin
3937
int y = p[5];
4038
#pragma clang unsafe_buffer_usage end
@@ -47,7 +45,8 @@ int base(int *p) {
4745

4846
#ifdef __cplusplus
4947
int sub1(int *p) {
50-
int x = p[5];
48+
int x = p[5]; // expected-warning{{unsafe buffer access}}\
49+
expected-note{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}}
5150
#pragma clang unsafe_buffer_usage begin
5251
int y = p[5];
5352
#pragma clang unsafe_buffer_usage end
@@ -69,7 +68,8 @@ T sub1_T(T *p) {
6968

7069
#ifdef __cplusplus
7170
int sub2(int *p) {
72-
int x = p[5];
71+
int x = p[5]; // expected-warning{{unsafe buffer access}}\
72+
expected-note{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}}
7373
#pragma clang unsafe_buffer_usage begin
7474
int y = p[5];
7575
#pragma clang unsafe_buffer_usage end
@@ -86,33 +86,25 @@ int textual(int *p) {
8686
}
8787
#endif
8888

89-
//--- safe_buffers_optout-explicit.cpp
90-
#include "test_sub1.h"
91-
#include "test_sub2.h"
89+
// Specify modules explicitly:
90+
// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodule-file=%t/safe_buffers_test_optout.pcm -I %t \
91+
// RUN: -std=c++20 -Wunsafe-buffer-usage -verify=main %t/safe_buffers_optout_main.cpp
9292

93-
// Testing safe buffers opt-out region serialization with modules: this
94-
// file loads 2 submodules from top-level module
95-
// `safe_buffers_test_optout`, which uses another top-level module
96-
// `safe_buffers_test_base`. (So the module dependencies form a DAG.)
93+
// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodule-file=%t/safe_buffers_test_optout.pcm -I %t \
94+
// RUN: -std=c++20 -Wunsafe-buffer-usage -verify=main-fixit %t/safe_buffers_optout_main.cpp \
95+
// RUN: -fsafe-buffer-usage-suggestions
9796

98-
// No expected warnings from base.h, test_sub1, or test_sub2 because they are
99-
// in separate modules, and the explicit commands that builds them have no
100-
// `-Wunsafe-buffer-usage`.
97+
// Specify modules implicitly:
98+
// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -verify=main -fmodules-cache-path=%t \
99+
// RUN: -fmodule-map-file=%t/safe_buffers_test.modulemap -I%t\
100+
// RUN: -x c++ -std=c++20 -Wunsafe-buffer-usage %t/safe_buffers_optout_main.cpp
101101

102-
int foo(int * p) {
103-
int x = p[5]; // expected-warning{{unsafe buffer access}} expected-note{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}}
104-
#pragma clang unsafe_buffer_usage begin
105-
int y = p[5];
106-
#pragma clang unsafe_buffer_usage end
107-
sub1_T(p); // instantiate template
108-
return sub1(p) + sub2(p);
109-
}
102+
// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -verify=main-fixit -fmodules-cache-path=%t \
103+
// RUN: -fmodule-map-file=%t/safe_buffers_test.modulemap -I%t\
104+
// RUN: -x c++ -std=c++20 -Wunsafe-buffer-usage %t/safe_buffers_optout_main.cpp \
105+
// RUN: -fsafe-buffer-usage-suggestions
110106

111-
#pragma clang unsafe_buffer_usage begin
112-
#include "textual.h" // This header is textually included (i.e., it is in the same TU as %s), so warnings are suppressed
113-
#pragma clang unsafe_buffer_usage end
114-
115-
//--- safe_buffers_optout-implicit.cpp
107+
//--- safe_buffers_optout_main.cpp
116108
#include "test_sub1.h"
117109
#include "test_sub2.h"
118110

@@ -121,18 +113,20 @@ int foo(int * p) {
121113
// `safe_buffers_test_optout`, which uses another top-level module
122114
// `safe_buffers_test_base`. (So the module dependencies form a DAG.)
123115

124-
// No expected warnings from base.h, test_sub1, or test_sub2 because they are
125-
// in separate modules, and the explicit commands that builds them have no
126-
// `-Wunsafe-buffer-usage`.
127-
128-
int foo(int * p) {
129-
int x = p[5]; // expected-warning{{unsafe buffer access}} expected-note{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}}
116+
int foo(int * p) { // main-fixit-warning{{'p' is an unsafe pointer used for buffer access}} \
117+
main-fixit-note{{change type of 'p' to 'std::span' to preserve bounds information}}
118+
int x = p[5]; // main-warning{{unsafe buffer access}} \
119+
main-note{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}} \
120+
main-fixit-note{{used in buffer access here}}
130121
#pragma clang unsafe_buffer_usage begin
131122
int y = p[5];
132123
#pragma clang unsafe_buffer_usage end
133124
sub1_T(p); // instantiate template
134125
return sub1(p) + sub2(p);
135126
}
127+
// main-warning@test_sub1.h:15 {{unsafe buffer access}}
128+
// main-note@test_sub1.h:15 {{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}}
129+
// main-note@-5{{in instantiation of function template specialization 'sub1_T<int>' requested here}}
136130

137131
#pragma clang unsafe_buffer_usage begin
138132
#include "textual.h" // This header is textually included (i.e., it is in the same TU as %s), so warnings are suppressed

clang/test/PCH/unsafe-buffer-usage-pragma-pch-complex.cpp

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,24 @@
77
// RUN: mkdir -p %t
88
// RUN: split-file %s %t
99

10-
// RUN: %clang_cc1 -Wno-unused-value -std=c++20 -emit-pch -o %t/pch_2.h.pch %t/pch_2.h -x c++
11-
// RUN: %clang_cc1 -Wno-unused-value -std=c++20 -include-pch %t/pch_2.h.pch -emit-pch -o %t/pch_1.h.pch %t/pch_1.h -x c++
10+
// RUN: %clang_cc1 -Wno-unused-value -std=c++20 -emit-pch -o %t/pch_2.h.pch %t/pch_2.h -x c++ -Wunsafe-buffer-usage -verify
11+
// RUN: %clang_cc1 -Wno-unused-value -std=c++20 -include-pch %t/pch_2.h.pch -emit-pch -o %t/pch_1.h.pch %t/pch_1.h -x c++ -Wunsafe-buffer-usage -verify
1212
// RUN: %clang_cc1 -Wno-unused-value -std=c++20 -include-pch %t/pch_1.h.pch -verify %t/main.cpp -Wunsafe-buffer-usage
1313

14+
// With '-fsafe-buffer-usage-suggestions':
15+
16+
// RUN: %clang_cc1 -Wno-unused-value -std=c++20 -emit-pch -o %t/pch_2.h.pch %t/pch_2.h -x c++ -Wunsafe-buffer-usage -verify=with-fixit -fsafe-buffer-usage-suggestions
17+
// RUN: %clang_cc1 -Wno-unused-value -std=c++20 -include-pch %t/pch_2.h.pch -emit-pch -o %t/pch_1.h.pch %t/pch_1.h -x c++ -Wunsafe-buffer-usage -verify=with-fixit -fsafe-buffer-usage-suggestions
18+
// RUN: %clang_cc1 -Wno-unused-value -std=c++20 -include-pch %t/pch_1.h.pch %t/main.cpp -Wunsafe-buffer-usage -verify=with-fixit -fsafe-buffer-usage-suggestions
19+
1420

1521
//--- textual_1.h
1622
int a(int *s) {
1723
s[2]; // <- expected warning here
1824
#pragma clang unsafe_buffer_usage begin
1925
return s[1];
2026
#pragma clang unsafe_buffer_usage end
27+
2128
}
2229

2330
//--- textual_2.h
@@ -29,18 +36,21 @@ int b(int *s) {
2936
}
3037

3138
//--- pch_1.h
32-
#include "textual_2.h"
33-
39+
#include "textual_2.h" // with-fixit-no-diagnostics
40+
// expected-warning@textual_2.h:2{{unsafe buffer access}} \
41+
expected-note@textual_2.h:2{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}}
3442
int c(int *s) {
35-
s[2]; // <- expected warning here
43+
s[2]; // expected-warning{{unsafe buffer access}} \
44+
expected-note{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}}
3645
#pragma clang unsafe_buffer_usage begin
3746
return s[1];
3847
#pragma clang unsafe_buffer_usage end
3948
}
4049

4150
//--- pch_2.h
42-
int d(int *s) {
43-
s[2]; // <- expected warning here
51+
int d(int *s) { // with-fixit-no-diagnostics
52+
s[2]; // expected-warning{{unsafe buffer access}} \
53+
expected-note{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}}
4454
#pragma clang unsafe_buffer_usage begin
4555
return s[1];
4656
#pragma clang unsafe_buffer_usage end
@@ -51,12 +61,15 @@ int d(int *s) {
5161
#include "textual_1.h"
5262
// expected-warning@textual_1.h:2{{unsafe buffer access}} \
5363
expected-note@textual_1.h:2{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}}
54-
// expected-warning@textual_2.h:2{{unsafe buffer access}} \
55-
expected-note@textual_2.h:2{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}}
56-
// expected-warning@pch_1.h:4{{unsafe buffer access}} \
57-
expected-note@pch_1.h:4{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}}
58-
// expected-warning@pch_2.h:2{{unsafe buffer access}} \
59-
expected-note@pch_2.h:2{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}}
64+
65+
// with-fixit-warning@textual_1.h:1 {{'s' is an unsafe pointer used for buffer access}}
66+
// with-fixit-note@textual_1.h:2 {{used in buffer access here}}
67+
// with-fixit-warning@textual_2.h:1 {{'s' is an unsafe pointer used for buffer access}}
68+
// with-fixit-note@textual_2.h:2 {{used in buffer access here}}
69+
// with-fixit-warning@pch_1.h:4 {{'s' is an unsafe pointer used for buffer access}}
70+
// with-fixit-note@pch_1.h:5 {{used in buffer access here}}
71+
// with-fixit-warning@pch_2.h:1 {{'s' is an unsafe pointer used for buffer access}}
72+
// with-fixit-note@pch_2.h:2 {{used in buffer access here}}
6073
int main() {
6174
int s[] = {1, 2, 3};
6275
return a(s) + b(s) + c(s) + d(s);

0 commit comments

Comments
 (0)