Skip to content

Infer @PointerBounds macro from clang __counted_by parameters #77387

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 1 commit into from
Dec 11, 2024
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
4 changes: 4 additions & 0 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,10 @@ EXPERIMENTAL_FEATURE(WarnUnsafe, true)
/// Import unsafe C and C++ constructs as @unsafe.
EXPERIMENTAL_FEATURE(SafeInterop, true)

// Import bounds safety and lifetime attributes from interop headers to
// generate Swift wrappers with safe pointer types.
EXPERIMENTAL_FEATURE(SafeInteropWrappers, false)

/// Ignore resilience errors due to C++ types.
EXPERIMENTAL_FEATURE(AssumeResilientCxxTypes, true)

Expand Down
5 changes: 5 additions & 0 deletions include/swift/Option/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,11 @@ def enable_experimental_concise_pound_file : Flag<["-"],
Flags<[FrontendOption, ModuleInterfaceOption]>,
HelpText<"Enable experimental concise '#file' identifier">;

def enable_experimental_bounds_safety_interop :
Flag<["-"], "enable-experimental-bounds-safety-interop">,
Flags<[NoDriverOption, FrontendOption, HelpHidden, ModuleInterfaceOption]>,
HelpText<"Enable experimental C bounds safety interop code generation and config directives">;

def enable_experimental_cxx_interop :
Flag<["-"], "enable-experimental-cxx-interop">,
Flags<[NoDriverOption, FrontendOption, HelpHidden, ModuleInterfaceOption]>,
Expand Down
1 change: 1 addition & 0 deletions lib/AST/FeatureSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ static bool usesFeatureAllowUnsafeAttribute(Decl *decl) {

UNINTERESTING_FEATURE(WarnUnsafe)
UNINTERESTING_FEATURE(SafeInterop)
UNINTERESTING_FEATURE(SafeInteropWrappers)
UNINTERESTING_FEATURE(AssumeResilientCxxTypes)
UNINTERESTING_FEATURE(CoroutineAccessorsUnwindOnCallerError)
UNINTERESTING_FEATURE(CoroutineAccessorsAllocateInCallee)
Expand Down
3 changes: 3 additions & 0 deletions lib/ClangImporter/ClangImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,9 @@ void importer::getNormalInvocationArguments(
}
}

if (LangOpts.hasFeature(Feature::SafeInteropWrappers))
invocationArgStrs.push_back("-fexperimental-bounds-safety-attributes");

// Set C language options.
if (triple.isOSDarwin()) {
invocationArgStrs.insert(invocationArgStrs.end(), {
Expand Down
194 changes: 134 additions & 60 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ createFuncOrAccessor(ClangImporter::Implementation &impl, SourceLoc funcLoc,
std::optional<AccessorInfo> accessorInfo, DeclName name,
SourceLoc nameLoc, GenericParamList *genericParams,
ParameterList *bodyParams, Type resultTy, bool async,
bool throws, DeclContext *dc, ClangNode clangNode) {
bool throws, DeclContext *dc, ClangNode clangNode,
bool hasBoundsAnnotation) {
FuncDecl *decl;
if (accessorInfo) {
decl = AccessorDecl::create(
Expand All @@ -124,6 +125,9 @@ createFuncOrAccessor(ClangImporter::Implementation &impl, SourceLoc funcLoc,
genericParams, dc, clangNode);
}
impl.importSwiftAttrAttributes(decl);
if (hasBoundsAnnotation)
impl.importBoundsAttributes(decl);

return decl;
}

Expand Down Expand Up @@ -3272,7 +3276,8 @@ namespace {
}
return Impl.importFunctionParameterList(
dc, decl, nonSelfParams, decl->isVariadic(), allowNSUIntegerAsInt,
argNames, genericParams, /*resultType=*/nullptr);
argNames, genericParams, /*resultType=*/nullptr,
/*hasBoundsAnnotatedParam=*/nullptr);
}

Decl *
Expand Down Expand Up @@ -3690,6 +3695,7 @@ namespace {

bool importFuncWithoutSignature =
isa<clang::CXXMethodDecl>(decl) && Impl.importSymbolicCXXDecls;
bool hasBoundsAnnotation = false;
if (!dc->isModuleScopeContext() && !isa<clang::CXXMethodDecl>(decl)) {
// Handle initializers.
if (name.getBaseName().isConstructor()) {
Expand Down Expand Up @@ -3786,7 +3792,7 @@ namespace {
importedType = Impl.importFunctionParamsAndReturnType(
dc, decl, {decl->param_begin(), decl->param_size()},
decl->isVariadic(), isInSystemModule(dc), name, bodyParams,
templateParams);
templateParams, &hasBoundsAnnotation);
}

if (auto *mdecl = dyn_cast<clang::CXXMethodDecl>(decl)) {
Expand Down Expand Up @@ -3853,10 +3859,10 @@ namespace {
auto resultTy = importedType.getType();

FuncDecl *func =
createFuncOrAccessor(Impl, loc, accessorInfo, name,
nameLoc, genericParams, bodyParams, resultTy,
createFuncOrAccessor(Impl, loc, accessorInfo, name, nameLoc,
genericParams, bodyParams, resultTy,
/*async=*/false, /*throws=*/false, dc,
clangNode);
clangNode, hasBoundsAnnotation);
result = func;

if (!dc->isModuleScopeContext()) {
Expand Down Expand Up @@ -4899,12 +4905,14 @@ namespace {
}
}

auto result = createFuncOrAccessor(Impl,
/*funcLoc*/ SourceLoc(), accessorInfo,
importedName.getDeclName(),
/*nameLoc*/ SourceLoc(),
/*genericParams=*/nullptr, bodyParams,
resultTy, async, throws, dc, decl);
bool hasBoundsAnnotation =
false; // currently only implemented for functions
auto result = createFuncOrAccessor(
Impl,
/*funcLoc*/ SourceLoc(), accessorInfo, importedName.getDeclName(),
/*nameLoc*/ SourceLoc(),
/*genericParams=*/nullptr, bodyParams, resultTy, async, throws, dc,
decl, hasBoundsAnnotation);

result->setAccess(decl->isDirectMethod() ? AccessLevel::Public
: getOverridableAccessLevel(dc));
Expand Down Expand Up @@ -6544,7 +6552,8 @@ Decl *SwiftDeclConverter::importGlobalAsInitializer(
} else {
parameterList = Impl.importFunctionParameterList(
dc, decl, {decl->param_begin(), decl->param_end()}, decl->isVariadic(),
allowNSUIntegerAsInt, argNames, /*genericParams=*/{}, /*resultType=*/nullptr);
allowNSUIntegerAsInt, argNames, /*genericParams=*/{},
/*resultType=*/nullptr, /*hasBoundsAnnotatedParam=*/nullptr);
}
if (!parameterList)
return nullptr;
Expand Down Expand Up @@ -8344,6 +8353,53 @@ static bool importAsUnsafe(ClangImporter::Implementation &impl,
return false;
}

void ClangImporter::Implementation::importNontrivialAttribute(
Decl *MappedDecl, llvm::StringRef AttrString) {
bool cached = true;
while (true) {
// Dig out a source file we can use for parsing.
auto &sourceFile = getClangSwiftAttrSourceFile(
*MappedDecl->getDeclContext()->getParentModule(), AttrString, cached);

auto topLevelDecls = sourceFile.getTopLevelDecls();

// If we're using the cached version, check whether we can correctly
// clone the attribute.
if (cached) {
bool hasNonclonableAttribute = false;
for (auto decl : topLevelDecls) {
if (hasNonclonableAttribute)
break;

for (auto attr : decl->getAttrs()) {
if (!attr->canClone()) {
hasNonclonableAttribute = true;
break;
}
}
}

// We cannot clone one of the attributes. Go back and build a new
// source file without caching it.
if (hasNonclonableAttribute) {
cached = false;
continue;
}
}

// Collect the attributes from the synthesized top-level declaration in
// the source file. If we're using a cached copy, clone the attribute.
for (auto decl : topLevelDecls) {
SmallVector<DeclAttribute *, 2> attrs(decl->getAttrs().begin(),
decl->getAttrs().end());
for (auto attr : attrs) {
MappedDecl->getAttrs().add(cached ? attr->clone(SwiftContext) : attr);
}
}
break;
}
}

void
ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) {
auto ClangDecl =
Expand Down Expand Up @@ -8481,53 +8537,7 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) {
continue;
}

bool cached = true;
while (true) {
// Dig out a source file we can use for parsing.
auto &sourceFile = getClangSwiftAttrSourceFile(
*MappedDecl->getDeclContext()->getParentModule(),
swiftAttr->getAttribute(),
cached);

auto topLevelDecls = sourceFile.getTopLevelDecls();

// If we're using the cached version, check whether we can correctly
// clone the attribute.
if (cached) {
bool hasNonclonableAttribute = false;
for (auto decl : topLevelDecls) {
if (hasNonclonableAttribute)
break;

for (auto attr : decl->getAttrs()) {
if (!attr->canClone()) {
hasNonclonableAttribute = true;
break;
}
}
}

// We cannot clone one of the attributes. Go back and build a new
// source file without caching it.
if (hasNonclonableAttribute) {
cached = false;
continue;
}
}

// Collect the attributes from the synthesized top-level declaration in
// the source file. If we're using a cached copy, clone the attribute.
for (auto decl : topLevelDecls) {
SmallVector<DeclAttribute *, 2> attrs(decl->getAttrs().begin(),
decl->getAttrs().end());
for (auto attr : attrs) {
MappedDecl->getAttrs().add(cached ? attr->clone(SwiftContext)
: attr);
}
}

break;
}
importNontrivialAttribute(MappedDecl, swiftAttr->getAttribute());
}

if (seenUnsafe || importAsUnsafe(*this, ClangDecl, MappedDecl)) {
Expand Down Expand Up @@ -8613,6 +8623,70 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) {
}
}

namespace {
class PointerParamInfoPrinter {
public:
clang::ASTContext &ctx;
llvm::raw_ostream &out;
bool firstParam = true;
PointerParamInfoPrinter(clang::ASTContext &ctx, llvm::raw_ostream &out)
: ctx(ctx), out(out) {
out << "@PointerBounds(";
}
~PointerParamInfoPrinter() { out << ")"; }

void printCountedBy(const clang::CountAttributedType *CAT,
size_t pointerIndex) {
if (!firstParam) {
out << ", ";
} else {
firstParam = false;
}
clang::Expr *countExpr = CAT->getCountExpr();
bool isSizedBy = CAT->isCountInBytes();
out << ".";
if (isSizedBy)
out << "sizedBy";
else
out << "countedBy";
out << "(pointer: " << pointerIndex + 1 << ", ";
if (isSizedBy)
out << "size";
else
out << "count";
out << ": \"";
countExpr->printPretty(
out, {}, {ctx.getLangOpts()}); // TODO: map clang::Expr to Swift Expr
out << "\")";
}
};
} // namespace

void ClangImporter::Implementation::importBoundsAttributes(
FuncDecl *MappedDecl) {
assert(SwiftContext.LangOpts.hasFeature(Feature::SafeInteropWrappers));
auto ClangDecl =
dyn_cast_or_null<clang::FunctionDecl>(MappedDecl->getClangDecl());
// any function with safe pointer imports should have a clang decl
assert(ClangDecl);
if (!ClangDecl)
return;

llvm::SmallString<128> MacroString;
{
llvm::raw_svector_ostream out(MacroString);

PointerParamInfoPrinter printer(getClangASTContext(), out);
for (auto [index, param] : llvm::enumerate(ClangDecl->parameters())) {
if (auto CAT = param->getType()->getAs<clang::CountAttributedType>()) {
printer.printCountedBy(CAT, index);
}
}
}

importNontrivialAttribute(MappedDecl, MacroString);
}

static bool isUsingMacroName(clang::SourceManager &SM,
clang::SourceLocation loc,
StringRef MacroName) {
Expand Down
Loading