Skip to content

[ClangImporter] Look through __ended_by and __null_terminated #81630

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions lib/ClangImporter/ImportType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -421,17 +421,15 @@ namespace {
ImportResult VisitDynamicRangePointerType(
const clang::DynamicRangePointerType *type) {
// DynamicRangePointerType is a clang type representing a pointer with
// an "ended_by" type attribute for -fbounds-safety. For now, we don't
// import these into Swift.
return Type();
// an "ended_by" type attribute for -fbounds-safety.
return Visit(type->desugar());
}

ImportResult VisitValueTerminatedType(
const clang::ValueTerminatedType *type) {
// ValueTerminatedType is a clang type representing a pointer with
// a "terminated_by" type attribute for -fbounds-safety. For now, we don't
// import these into Swift.
return Type();
// a "terminated_by" type attribute for -fbounds-safety.
return Visit(type->desugar());
}

ImportResult VisitMemberPointerType(const clang::MemberPointerType *type) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#pragma once

#include <ptrcheck.h>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this header file guaranteed to be on every platform we deploy to?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah probably not, no

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or actually, maybe? We ship it with clang, and we ship clang with swift. So I think it ought to be there

#include <stdarg.h>

// Test __counted_by, __sized_by, __ended_by, __single, __indexable and __bidi_indexable pointers
// in function parameters, return values, nested and unnested, pointing to void, char and int.
// Also test VLAs, and incomplete pointer type with __counted_by, since they are pretty much the same
// as __counted_by pointers in the -fbounds-safety model.

#ifndef __unsafe_late_const
#define __unsafe_late_const
#endif

int * __counted_by(len) a(int *__counted_by(len) p, int len);
char * __counted_by(len) b(char *__counted_by(len) p, int len);
char * __sized_by(len) c(char *__sized_by(len) p, int len);
void * __sized_by(len) d(void *__sized_by(len) p, int len);
int * __sized_by(len) e(int *__sized_by(len) p, int len);

int * __ended_by(end) f(int *__ended_by(end) p, int * end);
void * __ended_by(end) g(void *__ended_by(end) p, void * end);

char * __null_terminated h(char *__null_terminated p);
const char * i(const char * p);

char * __single j(char *__single p);
void *__single k(void *__single p);

#if __has_ptrcheck
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are only __indexable and __bidi_indexable guarded with __has_ptrcheck?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other macros defined in ptrcheck.h are defined to nothing when -fbounds-safety is not enabled, these two are intentionally left undefined since they can affect ABI, so you don't want one TU thinking they are normal pointers and one TU expanding them to wide pointers

// __indexable and __bidi_indexable are not defined unless -fbounds-safety is enabled
char * __indexable l(char *__indexable p);
void *__indexable m(void *__indexable p);

char * __bidi_indexable n(char *__bidi_indexable p);
void * __bidi_indexable o(void *__bidi_indexable p);
#endif

#if !__cplusplus
// No VLAs in C++
void p(int len, int p[len]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this isn't allowed in C++? (And can you add a comment with your explanation?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VLAs are a C only feature

#endif
void q(int p[__counted_by(len)], int len);

void r(int * __counted_by(*len) *__single p, int *len);
char * __null_terminated *__null_terminated s(char * __null_terminated *__null_terminated p);
char * __single *__single t(char * __single *__single p);

const int len1 = 7;
int * __counted_by(len1) u(int * __counted_by(len1) p);

int len2 __unsafe_late_const;
int * __counted_by(len2) v(int * __counted_by(len2) p);

// -fbounds-safety can sometimes affect va_list
void w(va_list p);
13 changes: 13 additions & 0 deletions test/Interop/C/bounds-safety/Inputs/bounds-attributed-global.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once

#include <ptrcheck.h>

extern int len;
extern int a[__counted_by(len)]; // expected-note{{'a' declared here}}

const char * __null_terminated b = "b";

extern int * __single * __single c;
#if __has_ptrcheck
extern int * __bidi_indexable d;
#endif
90 changes: 90 additions & 0 deletions test/Interop/C/bounds-safety/Inputs/bounds-attributed-struct.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#pragma once

#include <ptrcheck.h>

// Test __counted_by, __sized_by, __ended_by, __single, __indexable and __bidi_indexable pointers
// in function parameters, return values, nested and unnested, pointing to void, char and int.
// Also test VLAs, and incomplete pointer type with __counted_by, since they are pretty much the same
// as __counted_by pointers in the -fbounds-safety model.

struct a {
int * __counted_by(len) a;
int len;
};
struct a *a(struct a);

struct b {
int * __sized_by(len) a;
int len;
};
struct b *b(struct b);

struct c {
char * __sized_by(len) a;
int len;
};
struct c *c(struct c);

struct d {
void * __sized_by(len) a;
int len;
};
struct d *d(struct d);

struct e {
void * __single a;
int * __single b;
};
struct e *e(struct e);

struct f {
const char * a;
char * __null_terminated b;
};
struct f *f(struct f);

#if __has_ptrcheck
struct g {
void * __bidi_indexable a;
int * __bidi_indexable b;
};
struct g *g(struct g);

struct h {
void * __indexable a;
int * __indexable b;
};
struct h *h(struct h);
#endif

struct i {
int len;
int a[__counted_by(len)]; // expected-note {{field 'a' unavailable (cannot import)}}
};
struct i *__single i(struct i *);

const int len1 = 7;
struct j {
int * __counted_by(len1) a;
void * __sized_by(len1) b;
};
struct j *j(struct j);

int len2 __unsafe_late_const;
struct k {
int * __counted_by(len2) a;
void * __sized_by(len2) b;
};
struct k *k(struct k);

struct l {
int * __ended_by(end) a;
int * end;
};
struct l *l(struct l);

struct m {
void * __ended_by(end) a;
void * end;
};
struct m *m(struct m);
12 changes: 12 additions & 0 deletions test/Interop/C/bounds-safety/Inputs/module.modulemap
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module BoundsAttributedFunction {
header "bounds-attributed-function.h"
export *
}
module BoundsAttributedGlobal {
header "bounds-attributed-global.h"
export *
}
module BoundsAttributedStruct {
header "bounds-attributed-struct.h"
export *
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// RUN: %target-swift-ide-test -Xcc -fexperimental-bounds-safety-attributes -print-module -module-to-print=BoundsAttributedFunction -I %S/Inputs -source-filename=x | %FileCheck %s --check-prefixes=CHECK,C-ONLY
// RUN: %target-swift-ide-test -Xcc -fexperimental-bounds-safety-attributes -print-module -module-to-print=BoundsAttributedFunction -I %S/Inputs -source-filename=x -cxx-interoperability-mode=default | %FileCheck %s
// RUN: %target-swift-ide-test -Xcc -fbounds-safety -disable-objc-interop -print-module -module-to-print=BoundsAttributedFunction -I %S/Inputs -source-filename=x | %FileCheck %s --check-prefixes=CHECK,BOUNDS-SAFETY,C-ONLY

// This test case checks that ClangImporter can import declarations using various bounds attributes,
// rather than being marked unavailable because of an unknown type.

// CHECK: func a(_ p: UnsafeMutablePointer<Int{{[0-9]+}}>!, _ len: Int{{[0-9]+}}) -> UnsafeMutablePointer<Int{{[0-9]+}}>!
// CHECK-NEXT: func b(_ p: UnsafeMutablePointer<CChar>!, _ len: Int{{[0-9]+}}) -> UnsafeMutablePointer<CChar>!
// CHECK-NEXT: func c(_ p: UnsafeMutablePointer<CChar>!, _ len: Int{{[0-9]+}}) -> UnsafeMutablePointer<CChar>!
// CHECK-NEXT: func d(_ p: UnsafeMutableRawPointer!, _ len: Int{{[0-9]+}}) -> UnsafeMutableRawPointer!
// CHECK-NEXT: func e(_ p: UnsafeMutablePointer<Int{{[0-9]+}}>!, _ len: Int{{[0-9]+}}) -> UnsafeMutablePointer<Int{{[0-9]+}}>!
// CHECK-NEXT: func f(_ p: UnsafeMutablePointer<Int{{[0-9]+}}>!, _ end: UnsafeMutablePointer<Int{{[0-9]+}}>!) -> UnsafeMutablePointer<Int{{[0-9]+}}>!
// CHECK-NEXT: func g(_ p: UnsafeMutableRawPointer!, _ end: UnsafeMutableRawPointer!) -> UnsafeMutableRawPointer!
// CHECK-NEXT: func h(_ p: UnsafeMutablePointer<CChar>!) -> UnsafeMutablePointer<CChar>!
// CHECK-NEXT: func i(_ p: UnsafePointer<CChar>!) -> UnsafePointer<CChar>!
// CHECK-NEXT: func j(_ p: UnsafeMutablePointer<CChar>!) -> UnsafeMutablePointer<CChar>!
// CHECK-NEXT: func k(_ p: UnsafeMutableRawPointer!) -> UnsafeMutableRawPointer!

// BOUNDS-SAFETY-NEXT: func l(_ p: UnsafeMutablePointer<CChar>!) -> UnsafeMutablePointer<CChar>!
// BOUNDS-SAFETY-NEXT: func m(_ p: UnsafeMutableRawPointer!) -> UnsafeMutableRawPointer!
// BOUNDS-SAFETY-NEXT: func n(_ p: UnsafeMutablePointer<CChar>!) -> UnsafeMutablePointer<CChar>!
// BOUNDS-SAFETY-NEXT: func o(_ p: UnsafeMutableRawPointer!) -> UnsafeMutableRawPointer!

// C-ONLY-NEXT: func p(_ len: Int{{[0-9]+}}, _ p: UnsafeMutablePointer<Int{{[0-9]+}}>!)

// CHECK-NEXT: func q(_ p: UnsafeMutablePointer<Int{{[0-9]+}}>!, _ len: Int{{[0-9]+}})
// CHECK-NEXT: func r(_ p: UnsafeMutablePointer<UnsafeMutablePointer<Int{{[0-9]+}}>?>!, _ len: UnsafeMutablePointer<Int{{[0-9]+}}>!)
// CHECK-NEXT: func s(_ p: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>!) -> UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>!
// CHECK-NEXT: func t(_ p: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>!) -> UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>!
// CHECK-NEXT: var len1: Int{{[0-9]+}} { get }
// CHECK-NEXT: func u(_ p: UnsafeMutablePointer<Int{{[0-9]+}}>!) -> UnsafeMutablePointer<Int{{[0-9]+}}>!
// CHECK-NEXT: var len2: Int{{[0-9]+}}
// CHECK-NEXT: func v(_ p: UnsafeMutablePointer<Int{{[0-9]+}}>!) -> UnsafeMutablePointer<Int{{[0-9]+}}>!


// RUN: %target-swift-frontend -Xcc -fexperimental-bounds-safety-attributes -emit-module -plugin-path %swift-plugin-dir -I %S/Inputs %s -D C_ONLY
// RUN: %target-swift-frontend -Xcc -fexperimental-bounds-safety-attributes -emit-module -plugin-path %swift-plugin-dir -I %S/Inputs %s -cxx-interoperability-mode=default
// RUN: %target-swift-frontend -Xcc -fbounds-safety -disable-objc-interop -emit-module -plugin-path %swift-plugin-dir -I %S/Inputs %s -D C_ONLY -D BOUNDS_SAFETY

import BoundsAttributedFunction

func call(_ mutIntPtr: UnsafeMutablePointer<CInt>,
_ mutCharPtr: UnsafeMutablePointer<CChar>,
_ mutRawPtr: UnsafeMutableRawPointer,
_ constCharPtr: UnsafePointer<CChar>,
_ mutMutIntPtrPtr: UnsafeMutablePointer<UnsafeMutablePointer<CInt>?>!,
_ mutMutCharPtrPtr: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>!,
_ int: CInt,
_ args: CVaListPointer) {
let _ = a(mutIntPtr, int)
let _ = b(mutCharPtr, int)
let _ = c(mutCharPtr, int)
let _ = d(mutRawPtr, int)
let _ = e(mutIntPtr, int)
let _ = f(mutIntPtr, mutIntPtr)
let _ = g(mutRawPtr, mutRawPtr)
let _ = h(mutCharPtr)
let _ = i(constCharPtr)
let _ = j(mutCharPtr)
let _ = k(mutRawPtr)

#if BOUNDS_SAFETY
let _ = l(mutIntPtr)
let _ = m(mutRawPtr)
let _ = n(mutIntPtr)
let _ = o(mutRawPtr)
#endif

#if C_ONLY
let _ = p(int, mutIntPtr)
#endif

let _ = q(mutIntPtr, int)
let _ = r(mutMutIntPtrPtr, mutIntPtr)
let _ = s(mutMutCharPtrPtr)
let _ = t(mutMutCharPtrPtr)
let _ = len1
let _ = u(mutIntPtr)
let _ = len2
len2 = 37
let _ = v(mutIntPtr)

let _ = w(args)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// RUN: %target-swift-ide-test -Xcc -fexperimental-bounds-safety-attributes -print-module -module-to-print=BoundsAttributedGlobal -I %S/Inputs -source-filename=x | %FileCheck %s --check-prefixes=CHECK,C-ONLY
// RUN: %target-swift-ide-test -Xcc -fexperimental-bounds-safety-attributes -print-module -module-to-print=BoundsAttributedGlobal -I %S/Inputs -source-filename=x -cxx-interoperability-mode=swift-6 -Xcc -std=c++20 | %FileCheck %s
// RUN: %target-swift-ide-test -Xcc -fbounds-safety -disable-objc-interop -print-module -module-to-print=BoundsAttributedGlobal -I %S/Inputs -source-filename=x | %FileCheck %s --check-prefixes=CHECK,BOUNDS-SAFETY,C-ONLY

// This test case checks that ClangImporter can import declarations using various bounds attributes,
// rather than being marked unavailable because of an unknown type.

// CHECK: var len: Int32
// CHECK-NEXT: var a: <<error type>>
// CHECK-NEXT: var b: UnsafePointer<CChar>!
// CHECK-NEXT: var c: UnsafeMutablePointer<UnsafeMutablePointer<Int32>?>!
// BOUNDS-SAFETY-NEXT: var d: UnsafeMutablePointer<Int32>!


// RUN: %target-swift-frontend -Xcc -fexperimental-bounds-safety-attributes -emit-module -plugin-path %swift-plugin-dir -I %S/Inputs %s
// RUN: %target-swift-frontend -Xcc -fexperimental-bounds-safety-attributes -emit-module -plugin-path %swift-plugin-dir -I %S/Inputs %s -cxx-interoperability-mode=swift-6
// RUN: %target-swift-frontend -Xcc -fbounds-safety -disable-objc-interop -emit-module -plugin-path %swift-plugin-dir -I %S/Inputs %s -D BOUNDS_SAFETY

// RUN: %target-swift-frontend -Xcc -fexperimental-bounds-safety-attributes -emit-module -plugin-path %swift-plugin-dir -I %S/Inputs %s -verify -verify-additional-file %S/Inputs/bounds-attributed-global.h -D VERIFY
// RUN: %target-swift-frontend -Xcc -fexperimental-bounds-safety-attributes -emit-module -plugin-path %swift-plugin-dir -I %S/Inputs %s -cxx-interoperability-mode=swift-6 -verify -verify-additional-file %S/Inputs/bounds-attributed-global.h -D VERIFY
// RUN: %target-swift-frontend -Xcc -fbounds-safety -disable-objc-interop -emit-module -plugin-path %swift-plugin-dir -I %S/Inputs %s -verify -verify-additional-file %S/Inputs/bounds-attributed-global.h -D BOUNDS_SAFETY -D VERIFY

import BoundsAttributedGlobal

func access() {
#if VERIFY
// rdar://152293598 ([ClangImporter] Importing global array errors on macOS and Linux, but not on Windows)
// XFAIL: OS=windows-msvc
let _ = a // expected-error{{cannot reference invalid declaration 'a'}} rdar://151665752
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@egorzhdan Do you have any idea why this wouldn't error on Windows?

#endif
let _ = b.pointee
let _ = c.pointee!.pointee
#if BOUNDS_SAFETY
let _ = d.pointee
#endif
}
Loading