Skip to content

Commit

Permalink
Warning for framework include violation from Headers to PrivateHeaders
Browse files Browse the repository at this point in the history
Framework vendors usually layout their framework headers in the
following way:

Foo.framework/Headers -> "public" headers
Foo.framework/PrivateHeader -> "private" headers

Since both headers in both directories can be found with #import
<Foo/some-header.h>, it's easy to make mistakes and include headers in
Foo.framework/PrivateHeader from headers in Foo.framework/Headers, which
usually configures a layering violation on Darwin ecosystems. One of the
problem this causes is dep cycles when modules are used, since it's very
common for "private" modules to include from the "public" ones; adding
an edge the other way around will trigger cycles.

Add a warning to catch those cases such that:

./A.framework/Headers/A.h:1:10: warning: public framework header includes private framework header 'A/APriv.h'
#include <A/APriv.h>
         ^

rdar://problem/38712182

llvm-svn: 335542
  • Loading branch information
bcardosolopes committed Jun 25, 2018
1 parent 9bca748 commit 1b3b69f
Show file tree
Hide file tree
Showing 16 changed files with 147 additions and 5 deletions.
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticGroups.td
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ def Availability : DiagGroup<"availability">;
def Section : DiagGroup<"section">;
def AutoImport : DiagGroup<"auto-import">;
def FrameworkHdrQuotedInclude : DiagGroup<"quoted-include-in-framework-header">;
def FrameworkIncludePrivateFromPublic :
DiagGroup<"framework-include-private-from-public">;
def CXX14BinaryLiteral : DiagGroup<"c++14-binary-literal">;
def CXXPre14CompatBinaryLiteral : DiagGroup<"c++98-c++11-compat-binary-literal">;
def GNUBinaryLiteral : DiagGroup<"gnu-binary-literal">;
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticLexKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,9 @@ def warn_quoted_include_in_framework_header : Warning<
"double-quoted include \"%0\" in framework header, "
"expected angle-bracketed instead"
>, InGroup<FrameworkHdrQuotedInclude>, DefaultIgnore;
def warn_framework_include_private_from_public : Warning<
"public framework header includes private framework header '%0'"
>, InGroup<FrameworkIncludePrivateFromPublic>;

def warn_auto_module_import : Warning<
"treating #%select{include|import|include_next|__include_macros}0 as an "
Expand Down
25 changes: 20 additions & 5 deletions clang/lib/Lex/HeaderSearch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -621,11 +621,12 @@ static const char *copyString(StringRef Str, llvm::BumpPtrAllocator &Alloc) {
return CopyStr;
}

static bool isFrameworkStylePath(StringRef Path,
static bool isFrameworkStylePath(StringRef Path, bool &IsPrivateHeader,
SmallVectorImpl<char> &FrameworkName) {
using namespace llvm::sys;
path::const_iterator I = path::begin(Path);
path::const_iterator E = path::end(Path);
IsPrivateHeader = false;

// Detect different types of framework style paths:
//
Expand All @@ -637,12 +638,16 @@ static bool isFrameworkStylePath(StringRef Path,
// and some other variations among these lines.
int FoundComp = 0;
while (I != E) {
if (*I == "Headers")
++FoundComp;
if (I->endswith(".framework")) {
FrameworkName.append(I->begin(), I->end());
++FoundComp;
}
if (*I == "Headers" || *I == "PrivateHeaders")
if (*I == "PrivateHeaders") {
++FoundComp;
IsPrivateHeader = true;
}
++I;
}

Expand All @@ -654,11 +659,13 @@ diagnoseFrameworkInclude(DiagnosticsEngine &Diags, SourceLocation IncludeLoc,
StringRef Includer, StringRef IncludeFilename,
const FileEntry *IncludeFE, bool isAngled = false,
bool FoundByHeaderMap = false) {
bool IsIncluderPrivateHeader = false;
SmallString<128> FromFramework, ToFramework;
if (!isFrameworkStylePath(Includer, FromFramework))
if (!isFrameworkStylePath(Includer, IsIncluderPrivateHeader, FromFramework))
return;
bool IsIncludeeInFramework =
isFrameworkStylePath(IncludeFE->getName(), ToFramework);
bool IsIncludeePrivateHeader = false;
bool IsIncludeeInFramework = isFrameworkStylePath(
IncludeFE->getName(), IsIncludeePrivateHeader, ToFramework);

if (!isAngled && !FoundByHeaderMap) {
SmallString<128> NewInclude("<");
Expand All @@ -672,6 +679,14 @@ diagnoseFrameworkInclude(DiagnosticsEngine &Diags, SourceLocation IncludeLoc,
<< IncludeFilename
<< FixItHint::CreateReplacement(IncludeLoc, NewInclude);
}

// Headers in Foo.framework/Headers should not include headers
// from Foo.framework/PrivateHeaders, since this violates public/private
// API boundaries and can cause modular dependency cycles.
if (!IsIncluderPrivateHeader && IsIncludeeInFramework &&
IsIncludeePrivateHeader && FromFramework == ToFramework)
Diags.Report(IncludeLoc, diag::warn_framework_include_private_from_public)
<< IncludeFilename;
}

/// LookupFile - Given a "foo" or \<foo> reference, look up the indicated file,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#include <A/APriv.h>
#include "APriv2.h"
#include <Z/Z.h>
int foo();
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// framework-public-includes-private/A.framework/Modules/module.modulemap
framework module A {
header "A.h"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// framework-public-includes-private/A.framework/Modules/module.private.modulemap
framework module A_Private {
header "APriv.h"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// framework-public-includes-private/A.framework/PrivateHeaders/APriv.h
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// framework-public-includes-private/A.framework/PrivateHeaders/APriv2.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

{
"mappings" :
{
"A.h" : "A/A.h",
"APriv2.h" : "A/APriv2.h"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// framework-public-includes-private/flat-header-path/Z.h
#import "ZPriv.h"
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// framework-public-includes-private/flat-header-path/Z.modulemap
framework module Z {
header "Z.h"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// framework-public-includes-private/flat-header-path/Z.private.modulemap
framework module Z_Private {
header "ZPriv.h"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// framework-public-includes-private/flat-header-path/ZPriv.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

{
"mappings" :
{
"ZPriv.h" : "Z/ZPriv.h"
}
}
45 changes: 45 additions & 0 deletions clang/test/Modules/Inputs/framework-public-includes-private/z.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
'version': 0,
'case-sensitive': 'false',
'use-external-names' : 'false',
'roots': [
{
'type': 'directory',
'name': "TEST_DIR/Z.framework/Headers",
'contents': [
{
'type': 'file',
'name': "Z.h",
'external-contents': "TEST_DIR/flat-header-path/Z.h"
}
]
},
{
'type': 'directory',
'name': "TEST_DIR/Z.framework/PrivateHeaders",
'contents': [
{
'type': 'file',
'name': "ZPriv.h",
'external-contents': "TEST_DIR/flat-header-path/ZPriv.h"
}
]
},
{
'type': 'directory',
'name': "TEST_DIR/Z.framework/Modules",
'contents': [
{
'type': 'file',
'name': "module.modulemap",
'external-contents': "TEST_DIR/flat-header-path/Z.modulemap"
},
{
'type': 'file',
'name': "module.private.modulemap",
'external-contents': "TEST_DIR/flat-header-path/Z.private.modulemap"
}
]
}
]
}
37 changes: 37 additions & 0 deletions clang/test/Modules/framework-public-includes-private.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// REQUIRES: shell

// RUN: rm -rf %t
// RUN: mkdir %t

// RUN: hmaptool write %S/Inputs/framework-public-includes-private/a.hmap.json %t/a.hmap
// RUN: hmaptool write %S/Inputs/framework-public-includes-private/z.hmap.json %t/z.hmap

// RUN: sed -e "s:TEST_DIR:%S/Inputs/framework-public-includes-private:g" \
// RUN: %S/Inputs/framework-public-includes-private/z.yaml > %t/z.yaml

// The output with and without modules should be the same, without modules first.
// RUN: %clang_cc1 \
// RUN: -iquote %t/z.hmap -iquote %t/a.hmap -ivfsoverlay %t/z.yaml \
// RUN: -F%S/Inputs/framework-public-includes-private \
// RUN: -fsyntax-only %s -verify

// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/cache \
// RUN: -iquote %t/z.hmap -iquote %t/a.hmap -ivfsoverlay %t/z.yaml \
// RUN: -F%S/Inputs/framework-public-includes-private \
// RUN: -fsyntax-only %s \
// RUN: 2>%t/stderr

// The same warnings show up when modules is on but -verify doesn't get it
// because they only show up under the module A building context.
// RUN: FileCheck --input-file=%t/stderr %s
// CHECK: public framework header includes private framework header 'A/APriv.h'
// CHECK: public framework header includes private framework header 'A/APriv2.h'
// CHECK: public framework header includes private framework header 'Z/ZPriv.h'

#import "A.h"

int bar() { return foo(); }

// expected-warning@Inputs/framework-public-includes-private/A.framework/Headers/A.h:1{{public framework header includes private framework header 'A/APriv.h'}}
// expected-warning@Inputs/framework-public-includes-private/A.framework/Headers/A.h:2{{public framework header includes private framework header 'A/APriv2.h'}}
// expected-warning@Inputs/framework-public-includes-private/flat-header-path/Z.h:2{{public framework header includes private framework header 'Z/ZPriv.h'}}

0 comments on commit 1b3b69f

Please sign in to comment.