diff options
Diffstat (limited to 'clang/test/Analysis')
5 files changed, 149 insertions, 9 deletions
diff --git a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h index dacb713..a5fc3d7 100644 --- a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h +++ b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h @@ -178,6 +178,22 @@ __attribute__((objc_root_class)) + (NSNumber *)numberWithBool:(BOOL)value; @end +@interface NSResponder : NSObject +@end + +@interface NSApplication : NSResponder + +extern NSApplication * NSApp; + +@property (class, readonly, strong) NSApplication *sharedApplication; + +- (void)finishLaunching; +- (void)run; +- (void)stop:(id)sender; +- (void)terminate:(id)sender; + +@end + @interface SomeObj : NSObject - (instancetype)_init; - (SomeObj *)mutableCopy; diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm b/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm index c9d2fe8..a517dbc 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm @@ -398,12 +398,18 @@ namespace call_with_cf_constant { void baz(const NSDictionary *); void boo(NSNumber *); void boo(CFTypeRef); - void foo() { + + struct Details { + int value; + }; + + void foo(Details* details) { CFArrayCreateMutable(kCFAllocatorDefault, 10); bar(@[@"hello"]); baz(@{@"hello": @3}); boo(@YES); boo(@NO); + boo(@(details->value)); } } @@ -582,6 +588,7 @@ struct Derived : Base { [self doWork:@"hello", RetainPtr<SomeObj> { provide() }.get(), RetainPtr<CFMutableArrayRef> { provide_cf() }.get(), OSObjectPtr { provide_dispatch() }.get()]; [self doWork:__null]; [self doWork:nil]; + [NSApp run]; } - (SomeObj *)getSomeObj { diff --git a/clang/test/Analysis/Inputs/system-header-simulator-for-protobuf.h b/clang/test/Analysis/Inputs/system-header-simulator-for-protobuf.h new file mode 100644 index 0000000..cb12b55 --- /dev/null +++ b/clang/test/Analysis/Inputs/system-header-simulator-for-protobuf.h @@ -0,0 +1,18 @@ +// Like the compiler, the static analyzer treats some functions differently if +// they come from a system header -- for example, it is assumed that system +// functions do not arbitrarily free() their parameters, and that some bugs +// found in system headers cannot be fixed by the user and should be +// suppressed. +#pragma clang system_header + +class Arena; +class MessageLite { + int SomeArbitraryField; +}; + +// Originally declared in generated_message_util.h +MessageLite *GetOwnedMessageInternal(Arena *, MessageLite *, Arena *); + +// Not a real protobuf function -- just introduced to validate that this file +// is handled as a system header. +void SomeOtherFunction(MessageLite *); diff --git a/clang/test/Analysis/LifetimeSafety/benchmark.py b/clang/test/Analysis/LifetimeSafety/benchmark.py index d2e5f0b..cd5b3081 100644 --- a/clang/test/Analysis/LifetimeSafety/benchmark.py +++ b/clang/test/Analysis/LifetimeSafety/benchmark.py @@ -145,6 +145,60 @@ def generate_cpp_nested_loop_test(n: int) -> str: return cpp_code +def generate_cpp_switch_fan_out_test(n: int) -> str: + """ + Generates C++ code with a switch statement with N branches. + Each branch 'i' 'uses' (reads) a single, unique pointer 'pi'. + This pattern creates a "fan-in" join point for the backward + liveness analysis, stressing the LivenessMap::join operation + by forcing it to merge N disjoint, single-element sets of live origins. + The resulting complexity for LiveOrigins should be O(n log n) or higher. + + Example (n=3): + struct MyObj { int id; ~MyObj() {} }; + + void switch_fan_out_3(int condition) { + MyObj v1{1}; MyObj v2{1}; MyObj v3{1}; + MyObj* p1 = &v1; MyObj* p2 = &v2; MyObj* p3 = &v3; + + switch (condition % 3) { + case 0: + p1->id = 1; + break; + case 1: + p2->id = 1; + break; + case 2: + p3->id = 1; + break; + } + } + """ + if n <= 0: + return "// Number of variables must be positive." + + cpp_code = "struct MyObj { int id; ~MyObj() {} };\n\n" + cpp_code += f"void switch_fan_out{n}(int condition) {{\n" + # Generate N distinct objects + for i in range(1, n + 1): + cpp_code += f" MyObj v{i}{{1}};\n" + cpp_code += "\n" + # Generate N distinct pointers, each as a separate variable + for i in range(1, n + 1): + cpp_code += f" MyObj* p{i} = &v{i};\n" + cpp_code += "\n" + + cpp_code += f" switch (condition % {n}) {{\n" + for case_num in range(n): + cpp_code += f" case {case_num}:\n" + cpp_code += f" p{case_num + 1}->id = 1;\n" + cpp_code += " break;\n" + + cpp_code += " }\n}\n" + cpp_code += f"\nint main() {{ switch_fan_out{n}(0); return 0; }}\n" + return cpp_code + + def analyze_trace_file(trace_path: str) -> dict: """ Parses the -ftime-trace JSON output to find durations for the lifetime @@ -156,14 +210,14 @@ def analyze_trace_file(trace_path: str) -> dict: "total_us": 0.0, "fact_gen_us": 0.0, "loan_prop_us": 0.0, - "expired_loans_us": 0.0, + "live_origins_us": 0.0, } event_name_map = { "LifetimeSafetyAnalysis": "lifetime_us", "ExecuteCompiler": "total_us", "FactGenerator": "fact_gen_us", "LoanPropagation": "loan_prop_us", - "ExpiredLoans": "expired_loans_us", + "LiveOrigins": "live_origins_us", } try: with open(trace_path, "r") as f: @@ -227,7 +281,7 @@ def generate_markdown_report(results: dict) -> str: # Table header report.append( - "| N (Input Size) | Total Time | Analysis Time (%) | Fact Generator (%) | Loan Propagation (%) | Expired Loans (%) |" + "| N (Input Size) | Total Time | Analysis Time (%) | Fact Generator (%) | Loan Propagation (%) | Live Origins (%) |" ) report.append( "|:---------------|-----------:|------------------:|-------------------:|---------------------:|------------------:|" @@ -247,7 +301,7 @@ def generate_markdown_report(results: dict) -> str: f"{data['lifetime_ms'][i] / total_t * 100:>17.2f}% |", f"{data['fact_gen_ms'][i] / total_t * 100:>18.2f}% |", f"{data['loan_prop_ms'][i] / total_t * 100:>20.2f}% |", - f"{data['expired_loans_ms'][i] / total_t * 100:>17.2f}% |", + f"{data['live_origins_ms'][i] / total_t * 100:>17.2f}% |", ] report.append(" ".join(row)) @@ -259,7 +313,7 @@ def generate_markdown_report(results: dict) -> str: "Total Analysis": data["lifetime_ms"], "FactGenerator": data["fact_gen_ms"], "LoanPropagation": data["loan_prop_ms"], - "ExpiredLoans": data["expired_loans_ms"], + "LiveOrigins": data["live_origins_ms"], } for phase_name, y_data in analysis_phases.items(): @@ -302,7 +356,13 @@ def run_single_test( source_file, ] - result = subprocess.run(clang_command, capture_output=True, text=True, timeout=60) + try: + result = subprocess.run( + clang_command, capture_output=True, text=True, timeout=60 + ) + except subprocess.TimeoutExpired: + print(f"Compilation timed out for N={n}!", file=sys.stderr) + return {} if result.returncode != 0: print(f"Compilation failed for N={n}!", file=sys.stderr) @@ -354,6 +414,12 @@ if __name__ == "__main__": "generator_func": generate_cpp_nested_loop_test, "n_values": [50, 100, 150, 200], }, + { + "name": "switch_fan_out", + "title": "Switch Fan-out", + "generator_func": generate_cpp_switch_fan_out_test, + "n_values": [500, 1000, 2000, 4000], + }, ] results = {} @@ -368,7 +434,7 @@ if __name__ == "__main__": "total_ms": [], "fact_gen_ms": [], "loan_prop_ms": [], - "expired_loans_ms": [], + "live_origins_ms": [], } for n in config["n_values"]: durations_ms = run_single_test( @@ -387,7 +453,7 @@ if __name__ == "__main__": f" Total Analysis: {human_readable_time(durations_ms['lifetime_ms'])} | " f"FactGen: {human_readable_time(durations_ms['fact_gen_ms'])} | " f"LoanProp: {human_readable_time(durations_ms['loan_prop_ms'])} | " - f"ExpiredLoans: {human_readable_time(durations_ms['expired_loans_ms'])}" + f"LiveOrigins: {human_readable_time(durations_ms['live_origins_ms'])}" ) print("\n\n" + "=" * 80) diff --git a/clang/test/Analysis/NewDeleteLeaks.cpp b/clang/test/Analysis/NewDeleteLeaks.cpp index b2bad7e..d9c4b77 100644 --- a/clang/test/Analysis/NewDeleteLeaks.cpp +++ b/clang/test/Analysis/NewDeleteLeaks.cpp @@ -13,6 +13,8 @@ // RUN: unix.DynamicMemoryModeling:AddNoOwnershipChangeNotes=true #include "Inputs/system-header-simulator-for-malloc.h" +// For the tests in namespace protobuf_leak: +#include "Inputs/system-header-simulator-for-protobuf.h" //===----------------------------------------------------------------------===// // Report for which we expect NoOwnershipChangeVisitor to add a new note. @@ -218,3 +220,34 @@ void caller() { (void)n; } // no-warning: No potential memory leak here, because that's been already reported. } // namespace symbol_reaper_lifetime + +// Check that we do not report false positives in automatically generated +// protobuf code that passes dynamically allocated memory to a certain function +// named GetOwnedMessageInternal. +namespace protobuf_leak { +Arena *some_arena, *some_submessage_arena; + +MessageLite *protobuf_leak() { + MessageLite *p = new MessageLite(); // Real protobuf code instantiates a + // subclass of MessageLite, but that's + // not relevant for the bug. + MessageLite *q = GetOwnedMessageInternal(some_arena, p, some_submessage_arena); + return q; + // No leak at end of function -- the pointer escapes in GetOwnedMessageInternal. +} + +void validate_system_header() { + // The case protobuf_leak would also pass if GetOwnedMessageInternal wasn't + // declared in a system header. This test verifies that another function + // declared in the same header behaves differently (doesn't escape memory) to + // demonstrate that GetOwnedMessageInternal is indeed explicitly recognized + // by the analyzer. + + // expected-note@+1 {{Memory is allocated}} + MessageLite *p = new MessageLite(); + SomeOtherFunction(p); + // expected-warning@+2 {{Potential leak of memory pointed to by 'p'}} + // expected-note@+1 {{Potential leak of memory pointed to by 'p'}} +} + +} // namespace protobuf_leak |