Skip to content

Commit

Permalink
Add a libLTO API to query a memory buffer and check if it contains Ob…
Browse files Browse the repository at this point in the history
…jC categories

The linker supports a feature to force load an object from a static
archive if it defines an Objective-C category.
This API supports this feature by looking at every section in the
module to find if a category is defined in the module.

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@275125 91177308-0d34-0410-b5e6-96231b3b80d8
  • Loading branch information
joker-eph committed Jul 11, 2016
1 parent 9e93706 commit 45e997d
Show file tree
Hide file tree
Showing 8 changed files with 248 additions and 5 deletions.
18 changes: 13 additions & 5 deletions include/llvm-c/lto.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ typedef bool lto_bool_t;
* @{
*/

#define LTO_API_VERSION 19
#define LTO_API_VERSION 20

/**
* \since prior to LTO_API_VERSION=3
Expand Down Expand Up @@ -136,12 +136,20 @@ lto_module_is_object_file_for_target(const char* path,
const char* target_triple_prefix);

/**
* Checks if a buffer is a loadable object file.
* Return true if \p Buffer contains a bitcode file with ObjC code (category
* or class) in it.
*
* \since prior to LTO_API_VERSION=3
* \since LTO_API_VERSION=20
*/
extern lto_bool_t
lto_module_is_object_file_in_memory(const void* mem, size_t length);
bool lto_module_has_objc_category(const void *mem, size_t length);

/**
* Checks if a buffer is a loadable object file.
*
* \since prior to LTO_API_VERSION=3
*/
extern lto_bool_t lto_module_is_object_file_in_memory(const void *mem,
size_t length);

/**
* Checks if a buffer is a loadable object compiled for requested target.
Expand Down
5 changes: 5 additions & 0 deletions include/llvm/Bitcode/ReaderWriter.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ namespace llvm {
std::string getBitcodeTargetTriple(MemoryBufferRef Buffer,
LLVMContext &Context);

/// Return true if \p Buffer contains a bitcode file with ObjC code (category
/// or class) in it.
bool isBitcodeContainingObjCCategory(MemoryBufferRef Buffer,
LLVMContext &Context);

/// Read the header of the specified bitcode buffer and extract just the
/// producer string information. If successful, this returns a string. On
/// error, this returns "".
Expand Down
90 changes: 90 additions & 0 deletions lib/Bitcode/Reader/BitcodeReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,10 @@ class BitcodeReader : public GVMaterializer {
/// Cheap mechanism to just extract the identification block out of bitcode.
ErrorOr<std::string> parseIdentificationBlock();

/// Peak at the module content and return true if any ObjC category or class
/// is found.
ErrorOr<bool> hasObjCCategory();

static uint64_t decodeSignRotatedValue(uint64_t V);

/// Materialize any deferred Metadata block.
Expand Down Expand Up @@ -450,6 +454,7 @@ class BitcodeReader : public GVMaterializer {
ArrayRef<uint64_t> Record);
std::error_code parseMetadataAttachment(Function &F);
ErrorOr<std::string> parseModuleTriple();
ErrorOr<bool> hasObjCCategoryInModule();
std::error_code parseUseLists();
std::error_code initStream(std::unique_ptr<DataStreamer> Streamer);
std::error_code initStreamFromBuffer();
Expand Down Expand Up @@ -4195,6 +4200,81 @@ std::error_code BitcodeReader::parseGlobalObjectAttachment(
return std::error_code();
}

ErrorOr<bool> BitcodeReader::hasObjCCategory() {
if (std::error_code EC = initStream(nullptr))
return EC;

// Sniff for the signature.
if (!hasValidBitcodeHeader(Stream))
return error("Invalid bitcode signature");

// We expect a number of well-defined blocks, though we don't necessarily
// need to understand them all.
while (1) {
BitstreamEntry Entry = Stream.advance();

switch (Entry.Kind) {
case BitstreamEntry::Error:
return error("Malformed block");
case BitstreamEntry::EndBlock:
return std::error_code();

case BitstreamEntry::SubBlock:
if (Entry.ID == bitc::MODULE_BLOCK_ID)
return hasObjCCategoryInModule();

// Ignore other sub-blocks.
if (Stream.SkipBlock())
return error("Malformed block");
continue;

case BitstreamEntry::Record:
Stream.skipRecord(Entry.ID);
continue;
}
}
}

ErrorOr<bool> BitcodeReader::hasObjCCategoryInModule() {
if (Stream.EnterSubBlock(bitc::MODULE_BLOCK_ID))
return error("Invalid record");

SmallVector<uint64_t, 64> Record;
// Read all the records for this module.
while (1) {
BitstreamEntry Entry = Stream.advanceSkippingSubblocks();

switch (Entry.Kind) {
case BitstreamEntry::SubBlock: // Handled for us already.
case BitstreamEntry::Error:
return error("Malformed block");
case BitstreamEntry::EndBlock:
return false;
case BitstreamEntry::Record:
// The interesting case.
break;
}

// Read a record.
switch (Stream.readRecord(Entry.ID, Record)) {
default:
break; // Default behavior, ignore unknown content.
case bitc::MODULE_CODE_SECTIONNAME: { // SECTIONNAME: [strchr x N]
std::string S;
if (convertToString(Record, 0, S))
return error("Invalid record");
// Check for the i386 and other (x86_64, ARM) conventions
if (S.find("__DATA, __objc_catlist") != std::string::npos ||
S.find("__OBJC,__category") != std::string::npos)
return true;
break;
}
}
Record.clear();
}
llvm_unreachable("Exit infinite loop");
}

/// Parse metadata attachments.
std::error_code BitcodeReader::parseMetadataAttachment(Function &F) {
if (Stream.EnterSubBlock(bitc::METADATA_ATTACHMENT_ID))
Expand Down Expand Up @@ -6548,6 +6628,16 @@ std::string llvm::getBitcodeTargetTriple(MemoryBufferRef Buffer,
return Triple.get();
}

bool llvm::isBitcodeContainingObjCCategory(MemoryBufferRef Buffer,
LLVMContext &Context) {
std::unique_ptr<MemoryBuffer> Buf = MemoryBuffer::getMemBuffer(Buffer, false);
auto R = llvm::make_unique<BitcodeReader>(Buf.release(), Context);
ErrorOr<bool> hasObjCCategory = R->hasObjCCategory();
if (hasObjCCategory.getError())
return false;
return hasObjCCategory.get();
}

std::string llvm::getBitcodeProducerString(MemoryBufferRef Buffer,
LLVMContext &Context) {
std::unique_ptr<MemoryBuffer> Buf = MemoryBuffer::getMemBuffer(Buffer, false);
Expand Down
59 changes: 59 additions & 0 deletions test/LTO/X86/objc-detection-i386.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
; RUN: llvm-as < %s -o %t
; RUN: llvm-lto -check-for-objc %t | FileCheck %s

; CHECK: contains ObjC


target datalayout = "e-m:o-p:32:32-f64:32:64-f80:128-n8:16:32-S128"
target triple = "i386-apple-macosx10.12.0"

module asm "\09.lazy_reference .objc_class_name_A"
module asm "\09.objc_category_name_A_foo=0"
module asm "\09.globl .objc_category_name_A_foo"

%0 = type opaque
%struct._objc_method = type { i8*, i8*, i8* }
%struct._objc_category = type { i8*, i8*, %struct._objc_method_list*, %struct._objc_method_list*, %struct._objc_protocol_list*, i32, %struct._prop_list_t*, %struct._prop_list_t* }
%struct._objc_method_list = type opaque
%struct._objc_protocol_list = type { %struct._objc_protocol_list*, i32, [0 x %struct._objc_protocol] }
%struct._objc_protocol = type { %struct._objc_protocol_extension*, i8*, %struct._objc_protocol_list*, %struct._objc_method_description_list*, %struct._objc_method_description_list* }
%struct._objc_protocol_extension = type { i32, %struct._objc_method_description_list*, %struct._objc_method_description_list*, %struct._prop_list_t*, i8**, %struct._prop_list_t* }
%struct._objc_method_description_list = type { i32, [0 x %struct._objc_method_description] }
%struct._objc_method_description = type { i8*, i8* }
%struct._prop_list_t = type { i32, i32, [0 x %struct._prop_t] }
%struct._prop_t = type { i8*, i8* }
%struct._objc_module = type { i32, i32, i8*, %struct._objc_symtab* }
%struct._objc_symtab = type { i32, i8*, i16, i16, [0 x i8*] }

@OBJC_METH_VAR_NAME_ = private global [12 x i8] c"foo_myStuff\00", section "__TEXT,__cstring,cstring_literals", align 1
@OBJC_METH_VAR_TYPE_ = private global [7 x i8] c"v8@0:4\00", section "__TEXT,__cstring,cstring_literals", align 1
@OBJC_CLASS_NAME_ = private global [4 x i8] c"foo\00", section "__TEXT,__cstring,cstring_literals", align 1
@OBJC_CLASS_NAME_.1 = private global [2 x i8] c"A\00", section "__TEXT,__cstring,cstring_literals", align 1
@OBJC_CATEGORY_INSTANCE_METHODS_A_foo = private global { i8*, i32, [1 x %struct._objc_method] } { i8* null, i32 1, [1 x %struct._objc_method] [%struct._objc_method { i8* getelementptr inbounds ([12 x i8], [12 x i8]* @OBJC_METH_VAR_NAME_, i32 0, i32 0), i8* getelementptr inbounds ([7 x i8], [7 x i8]* @OBJC_METH_VAR_TYPE_, i32 0, i32 0), i8* bitcast (void (%0*, i8*)* @"\01-[A(foo) foo_myStuff]" to i8*) }] }, section "__OBJC,__cat_inst_meth,regular,no_dead_strip", align 4
@OBJC_CATEGORY_A_foo = private global %struct._objc_category { i8* getelementptr inbounds ([4 x i8], [4 x i8]* @OBJC_CLASS_NAME_, i32 0, i32 0), i8* getelementptr inbounds ([2 x i8], [2 x i8]* @OBJC_CLASS_NAME_.1, i32 0, i32 0), %struct._objc_method_list* bitcast ({ i8*, i32, [1 x %struct._objc_method] }* @OBJC_CATEGORY_INSTANCE_METHODS_A_foo to %struct._objc_method_list*), %struct._objc_method_list* null, %struct._objc_protocol_list* null, i32 32, %struct._prop_list_t* null, %struct._prop_list_t* null }, section "__OBJC,__category,regular,no_dead_strip", align 4
@OBJC_CLASS_NAME_.2 = private global [1 x i8] zeroinitializer, section "__TEXT,__cstring,cstring_literals", align 1
@OBJC_SYMBOLS = private global { i32, i8*, i16, i16, [1 x i8*] } { i32 0, i8* null, i16 0, i16 1, [1 x i8*] [i8* bitcast (%struct._objc_category* @OBJC_CATEGORY_A_foo to i8*)] }, section "__OBJC,__symbols,regular,no_dead_strip", align 4
@OBJC_MODULES = private global %struct._objc_module { i32 7, i32 16, i8* getelementptr inbounds ([1 x i8], [1 x i8]* @OBJC_CLASS_NAME_.2, i32 0, i32 0), %struct._objc_symtab* bitcast ({ i32, i8*, i16, i16, [1 x i8*] }* @OBJC_SYMBOLS to %struct._objc_symtab*) }, section "__OBJC,__module_info,regular,no_dead_strip", align 4
@llvm.compiler.used = appending global [9 x i8*] [i8* getelementptr inbounds ([12 x i8], [12 x i8]* @OBJC_METH_VAR_NAME_, i32 0, i32 0), i8* getelementptr inbounds ([7 x i8], [7 x i8]* @OBJC_METH_VAR_TYPE_, i32 0, i32 0), i8* getelementptr inbounds ([4 x i8], [4 x i8]* @OBJC_CLASS_NAME_, i32 0, i32 0), i8* getelementptr inbounds ([2 x i8], [2 x i8]* @OBJC_CLASS_NAME_.1, i32 0, i32 0), i8* bitcast ({ i8*, i32, [1 x %struct._objc_method] }* @OBJC_CATEGORY_INSTANCE_METHODS_A_foo to i8*), i8* bitcast (%struct._objc_category* @OBJC_CATEGORY_A_foo to i8*), i8* getelementptr inbounds ([1 x i8], [1 x i8]* @OBJC_CLASS_NAME_.2, i32 0, i32 0), i8* bitcast ({ i32, i8*, i16, i16, [1 x i8*] }* @OBJC_SYMBOLS to i8*), i8* bitcast (%struct._objc_module* @OBJC_MODULES to i8*)], section "llvm.metadata"

; Function Attrs: nounwind ssp
define internal void @"\01-[A(foo) foo_myStuff]"(%0*, i8*) #0 {
%3 = alloca %0*, align 4
%4 = alloca i8*, align 4
store %0* %0, %0** %3, align 4
store i8* %1, i8** %4, align 4
ret void
}

attributes #0 = { nounwind ssp "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0, !1, !2, !3, !4, !5}
!llvm.ident = !{!6}

!0 = !{i32 1, !"Objective-C Version", i32 1}
!1 = !{i32 1, !"Objective-C Image Info Version", i32 0}
!2 = !{i32 1, !"Objective-C Image Info Section", !"__OBJC, __image_info,regular"}
!3 = !{i32 4, !"Objective-C Garbage Collection", i32 0}
!4 = !{i32 1, !"Objective-C Class Properties", i32 64}
!5 = !{i32 1, !"PIC Level", i32 2}
!6 = !{!"Apple LLVM version 8.0.0 (clang-800.0.24.1)"}
52 changes: 52 additions & 0 deletions test/LTO/X86/objc-detection.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
; RUN: llvm-as < %s -o %t
; RUN: llvm-lto -check-for-objc %t | FileCheck %s

; CHECK: contains ObjC

target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.12.0"

%0 = type opaque
%struct._class_t = type { %struct._class_t*, %struct._class_t*, %struct._objc_cache*, i8* (i8*, i8*)**, %struct._class_ro_t* }
%struct._objc_cache = type opaque
%struct._class_ro_t = type { i32, i32, i32, i8*, i8*, %struct.__method_list_t*, %struct._objc_protocol_list*, %struct._ivar_list_t*, i8*, %struct._prop_list_t* }
%struct.__method_list_t = type { i32, i32, [0 x %struct._objc_method] }
%struct._objc_method = type { i8*, i8*, i8* }
%struct._objc_protocol_list = type { i64, [0 x %struct._protocol_t*] }
%struct._protocol_t = type { i8*, i8*, %struct._objc_protocol_list*, %struct.__method_list_t*, %struct.__method_list_t*, %struct.__method_list_t*, %struct.__method_list_t*, %struct._prop_list_t*, i32, i32, i8**, i8*, %struct._prop_list_t* }
%struct._ivar_list_t = type { i32, i32, [0 x %struct._ivar_t] }
%struct._ivar_t = type { i64*, i8*, i8*, i32, i32 }
%struct._prop_list_t = type { i32, i32, [0 x %struct._prop_t] }
%struct._prop_t = type { i8*, i8* }
%struct._category_t = type { i8*, %struct._class_t*, %struct.__method_list_t*, %struct.__method_list_t*, %struct._objc_protocol_list*, %struct._prop_list_t*, %struct._prop_list_t*, i32 }

@OBJC_CLASS_NAME_ = private global [4 x i8] c"foo\00", section "__TEXT,__objc_classname,cstring_literals", align 1
@"OBJC_CLASS_$_A" = external global %struct._class_t
@OBJC_METH_VAR_NAME_ = private global [12 x i8] c"foo_myStuff\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@OBJC_METH_VAR_TYPE_ = private global [8 x i8] c"v16@0:8\00", section "__TEXT,__objc_methtype,cstring_literals", align 1
@"\01l_OBJC_$_CATEGORY_INSTANCE_METHODS_A_$_foo" = private global { i32, i32, [1 x %struct._objc_method] } { i32 24, i32 1, [1 x %struct._objc_method] [%struct._objc_method { i8* getelementptr inbounds ([12 x i8], [12 x i8]* @OBJC_METH_VAR_NAME_, i32 0, i32 0), i8* getelementptr inbounds ([8 x i8], [8 x i8]* @OBJC_METH_VAR_TYPE_, i32 0, i32 0), i8* bitcast (void (%0*, i8*)* @"\01-[A(foo) foo_myStuff]" to i8*) }] }, section "__DATA, __objc_const", align 8
@"\01l_OBJC_$_CATEGORY_A_$_foo" = private global %struct._category_t { i8* getelementptr inbounds ([4 x i8], [4 x i8]* @OBJC_CLASS_NAME_, i32 0, i32 0), %struct._class_t* @"OBJC_CLASS_$_A", %struct.__method_list_t* bitcast ({ i32, i32, [1 x %struct._objc_method] }* @"\01l_OBJC_$_CATEGORY_INSTANCE_METHODS_A_$_foo" to %struct.__method_list_t*), %struct.__method_list_t* null, %struct._objc_protocol_list* null, %struct._prop_list_t* null, %struct._prop_list_t* null, i32 64 }, section "__DATA, __objc_const", align 8
@"OBJC_LABEL_CATEGORY_$" = private global [1 x i8*] [i8* bitcast (%struct._category_t* @"\01l_OBJC_$_CATEGORY_A_$_foo" to i8*)], section "__DATA, __objc_catlist, regular, no_dead_strip", align 8
@llvm.compiler.used = appending global [6 x i8*] [i8* getelementptr inbounds ([4 x i8], [4 x i8]* @OBJC_CLASS_NAME_, i32 0, i32 0), i8* getelementptr inbounds ([12 x i8], [12 x i8]* @OBJC_METH_VAR_NAME_, i32 0, i32 0), i8* getelementptr inbounds ([8 x i8], [8 x i8]* @OBJC_METH_VAR_TYPE_, i32 0, i32 0), i8* bitcast ({ i32, i32, [1 x %struct._objc_method] }* @"\01l_OBJC_$_CATEGORY_INSTANCE_METHODS_A_$_foo" to i8*), i8* bitcast (%struct._category_t* @"\01l_OBJC_$_CATEGORY_A_$_foo" to i8*), i8* bitcast ([1 x i8*]* @"OBJC_LABEL_CATEGORY_$" to i8*)], section "llvm.metadata"

; Function Attrs: ssp uwtable
define internal void @"\01-[A(foo) foo_myStuff]"(%0*, i8*) #0 {
%3 = alloca %0*, align 8
%4 = alloca i8*, align 8
store %0* %0, %0** %3, align 8
store i8* %1, i8** %4, align 8
ret void
}

attributes #0 = { ssp uwtable "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0, !1, !2, !3, !4, !5}
!llvm.ident = !{!6}

!0 = !{i32 1, !"Objective-C Version", i32 2}
!1 = !{i32 1, !"Objective-C Image Info Version", i32 0}
!2 = !{i32 1, !"Objective-C Image Info Section", !"__DATA, __objc_imageinfo, regular, no_dead_strip"}
!3 = !{i32 4, !"Objective-C Garbage Collection", i32 0}
!4 = !{i32 1, !"Objective-C Class Properties", i32 64}
!5 = !{i32 1, !"PIC Level", i32 2}
!6 = !{!"Apple LLVM version 8.0.0 (clang-800.0.24.1)"}
19 changes: 19 additions & 0 deletions tools/llvm-lto/llvm-lto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ static cl::opt<bool> RestoreGlobalsLinkage(
"restore-linkage", cl::init(false),
cl::desc("Restore original linkage of globals prior to CodeGen"));

static cl::opt<bool> CheckHasObjC(
"check-for-objc", cl::init(false),
cl::desc("Only check if the module has objective-C defined in it"));

namespace {
struct ModuleInfo {
std::vector<bool> CanBeHidden;
Expand Down Expand Up @@ -714,6 +718,21 @@ int main(int argc, char **argv) {
return 0;
}

if (CheckHasObjC) {
for (auto &Filename : InputFilenames) {
ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
MemoryBuffer::getFile(Filename);
error(BufferOrErr, "error loading file '" + Filename + "'");
auto Buffer = std::move(BufferOrErr.get());
LLVMContext Ctx;
if (llvm::isBitcodeContainingObjCCategory(*Buffer, Ctx))
outs() << "Bitcode " << Filename << " contains ObjC\n";
else
outs() << "Bitcode " << Filename << " does not contain ObjC\n";
}
return 0;
}

if (ThinLTOMode.getNumOccurrences()) {
if (ThinLTOMode.getNumOccurrences() > 1)
report_fatal_error("You can't specify more than one -thinlto-action");
Expand Down
9 changes: 9 additions & 0 deletions tools/lto/lto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include "llvm-c/lto.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Bitcode/ReaderWriter.h"
#include "llvm/CodeGen/CommandFlags.h"
#include "llvm/IR/DiagnosticInfo.h"
#include "llvm/IR/DiagnosticPrinter.h"
Expand Down Expand Up @@ -180,6 +181,14 @@ bool lto_module_is_object_file_for_target(const char* path,
return LTOModule::isBitcodeForTarget(Buffer->get(), target_triplet_prefix);
}

bool lto_module_has_objc_category(const void *mem, size_t length) {
std::unique_ptr<MemoryBuffer> Buffer(LTOModule::makeBuffer(mem, length));
if (!Buffer)
return false;
LLVMContext Ctx;
return llvm::isBitcodeContainingObjCCategory(*Buffer, Ctx);
}

bool lto_module_is_object_file_in_memory(const void* mem, size_t length) {
return LTOModule::isBitcodeFile(mem, length);
}
Expand Down
1 change: 1 addition & 0 deletions tools/lto/lto.exports
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ lto_module_is_object_file
lto_module_is_object_file_for_target
lto_module_is_object_file_in_memory
lto_module_is_object_file_in_memory_for_target
lto_module_has_objc_category
lto_module_dispose
lto_api_version
lto_codegen_set_diagnostic_handler
Expand Down

0 comments on commit 45e997d

Please sign in to comment.