Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Conversation

@gaaclarke
Copy link
Member

All of our unit tests are written with ARC. In order for us to write those tests, non-ARC code must live in the binary, not in the headers to avoid clang++ -fobjc-arc from attempting to compile them when including the headers of the classes it wants to test.

Related PR: #18281

@gaaclarke
Copy link
Member Author

@chinmaygarde is the outstanding concern that this may break the engine? Or that the tests might break? If if made you more comfortable I could make this a complete noop for the engine:

template <typename NST, typename MemoryManagement = scoped_nsprotocol_memory_management<NST>>
class scoped_nsprotocol {
 public:
  explicit scoped_nsprotocol(NST object = nil) : object_(object) {}

  scoped_nsprotocol(const scoped_nsprotocol<NST>& that) : object_(MemoryManagement::retain(that.object_)) {}

  template <typename NSU>
  scoped_nsprotocol(const scoped_nsprotocol<NSU>& that) : object_(MemoryManagement::retain(that.get())) {}

  ~scoped_nsprotocol() { MemoryManagement::release(object_); }

  scoped_nsprotocol& operator=(const scoped_nsprotocol<NST>& that) {
    reset(MemoryManagement::retain(that.get()));
    return *this;
  }
  ...

For the engine code, this PR changes nothing other than adding an extra C function on top of memory management code (that I can eliminate if we want the extra complexity). For testing code, there is no such thing as hypothetical bugs because 100% of the testing code is tested.

@stuartmorgan-g
Copy link
Contributor

This PR looks like what I remember our first attempt being. IIRC the issue here is something like: because it doesn't have the proper annotations, these functions behave differently when compiled in ARC vs non-ARC translation units. That means that if two TUs, one with ARC and one without, both include this header (which is going to be the case), you have two different implementations of the same function, and that violates the ODR. ODR violations result in undefined behavior. I saw fun things like ARC TUs getting the non-ARC codepath, and the other way around.

For testing code, there is no such thing as hypothetical bugs because 100% of the testing code is tested.

There is such a thing as undefined behavior though, and you can't test your away around it because it's undefined.

Is there a reason you're not just taking the working implementation from Chromium?

@gaaclarke
Copy link
Member Author

gaaclarke commented May 14, 2020

Thanks Stuart for the response.

these functions behave differently when compiled in ARC vs non-ARC translation units.

These functions are in a .mm file that is compiled into libFlutter and only compiled with non-ARC so there is no opportunity for them to behave differently. Ignore for a second that I'm doing this PR to support my ARC test. If you just look at the change you see this is the same behavior as the previous header when it is only included in non-ARC code.

ODR violations result in undefined behavior. I saw fun things like ARC TUs getting the non-ARC codepath, and the other way around.

We can see the non-ARC code will compile the same. With respect to ARC code, the only ARC code is test code which means we have 100% test coverage. If the tests pass there is no bug.

For testing code, there is no such thing as hypothetical bugs because 100% of the testing code is tested.

There is such a thing as undefined behavior though, and you can't test your away around it because it's undefined.

Undefined behavior isn't a problem for tests since everything is asserted to work a certain way. It is only a problem if the undefined behavior acts inconsistently across the same compiler and platform. There is no real way for me to prove that a compiler doesn't have undefined behavior, it is a risk we live with with all compiled code. I shouldn't have to prove 100% of all possible code creates expected results, especially when dealing with a situation where 100% of actual usage is known.

Is there a reason you're not just taking the working implementation from Chromium?

I noticed after the fact that they just threw an #error for ARC so it won't work.

Here's our options:

  1. The current proposition. The benefit being it addresses the issue across the board. There is a non-proven belief that it may have undefined behaviors, but they are limited to testing code.
  2. I could refactor AccessibilityBridge to use pimpl. I don't have to edit scoped_nsobject but it is also a game of whack-a-mole where we have to transition things to pimpl as they get included in headers. There is also a slight performance cost to production code.
  3. I could limit this change to opt in TU's via a FLUTTER_ENABLE_SCOPED_NSOBJECT_ARC macro. If the concern that someone may use this header in production code this would reduce that risk.
  4. I could rewrite the test to use manual reference counting. This would leave us with a confusing test bed where some tests are ARC, some are not. If we transitioned everything to non-ARC we would not be testing our code how the majority of people actually use our code. Also, I've had problems with OCMock in the past with non-ARC code (I don't miss the irony). Non-ARC code is a 3rd class citizen (behind Swift and ARC Objc) so it's easy to see why it isn't as tested for libraries like OCMock. We should be using it the way most people are using it when reasonable to do.

edit: cc @stuartmorgan

@gaaclarke gaaclarke requested a review from stuartmorgan-g May 14, 2020 16:52
@stuartmorgan-g
Copy link
Contributor

Undefined behavior isn't a problem for tests since everything is asserted to work a certain way. It is only a problem if the undefined behavior acts inconsistently across the same compiler and platform.

I don't see any reason to believe that the UB from an ODR violation wouldn't cause hard-to-find flaky test issues.

There is no real way for me to prove that a compiler doesn't have undefined behavior, it is a risk we live with with all compiled code. I shouldn't have to prove 100% of all possible code creates expected results, especially when dealing with a situation where 100% of actual usage is known.

There is a huge difference between "theoretically, all code could potentially have undefined behavior" and "this code is forked from code where I and some other people tried to solve exactly the problem you are trying to solve (i.e., transitive header includes of scoped_nsobject usage) and my recollection is that the approach you are trying is the one that gave us UB".

Is there a reason you're not just taking the working implementation from Chromium?

I noticed after the fact that they just threw an #error for ARC so it won't work.

That's a recent change, which apparently happened after they completed a full ARC transition and thus no longer needed to solve this problem. https://codereview.chromium.org/1855483004 is where they first made it work.

Here's our options:

  1. The current proposition. The benefit being it addresses the issue across the board. There is a non-proven belief that it may have undefined behaviors, but they are limited to testing code.
  2. I could refactor AccessibilityBridge to use pimpl. I don't have to edit scoped_nsobject but it is also a game of whack-a-mole where we have to transition things to pimpl as they get included in headers. There is also a slight performance cost to production code.
  3. I could limit this change to opt in TU's via a FLUTTER_ENABLE_SCOPED_NSOBJECT_ARC macro. If the concern that someone may use this header in production code this would reduce that risk.
  4. I could rewrite the test to use manual reference counting. This would leave us with a confusing test bed where some tests are ARC, some are not. If we transitioned everything to non-ARC we would not be testing our code how the majority of people actually use our code. Also, I've had problems with OCMock in the past with non-ARC code (I don't miss the irony). Non-ARC code is a 3rd class citizen (behind Swift and ARC Objc) so it's easy to see why it isn't as tested for libraries like OCMock. We should be using it the way most people are using it when reasonable to do.
  1. We could update our copy of scoped_nsobject—which came from Chromium in the first place—to a version where Chromium engineers already did the work of making it work correctly for exactly this use case.

Which like 1 address this across the board, but unlike 1 has been used extensively in production code, thus we have a high degree of confidence that it doesn't have ODR violations.

@gaaclarke
Copy link
Member Author

gaaclarke commented May 14, 2020

@stuartmorgan thanks for the link.

I read through their implementation, there is 2 things they are doing different than this PR. They are using extra annotations __unsafe_unretained and ns_returns_not_retained and they are only using the those functions when __has_feature(objc_arc).

I can make those 2 changes, or I can bring their code over. Their code is a bit messy and spread out across multiple files. I'd prefer to make those 2 changes but if you feel strongly about it I can try to bring over their files verbatim.

@gaaclarke
Copy link
Member Author

@stuartmorgan There is a lot of baggage bringing over chromes code verbatim. I moved over the difference I saw, please review an let me know if I missed something.

@gaaclarke
Copy link
Member Author

There is one more thing chrome does that we don't, InvalidValue() One sec I'll add that.

@gaaclarke
Copy link
Member Author

Added InvalidValue from chrome. That seemed unnecessary but worth keeping it the same.

@gaaclarke gaaclarke force-pushed the scoped-nsobject-arc branch from 0bbd485 to 0eda998 Compare May 14, 2020 22:03
static NST InvalidValue() { return nil; }
};

#if __has_feature(objc_arc)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm confused by this ifdef; why do you want different ifdef paths on the implementations? That seems like it significantly increases the chances of an accidental ODR violation. The same declarations and implementation of the wrappers should work for both. That was the whole point of the structure of the Chromium change.

In fact, it looks to me like this guarantees ODR violations, since you'll generate two copies of definitions of this template class (for any given NST), with different implementations. Am I missing something?

Copy link
Member Author

Choose a reason for hiding this comment

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

ifdef's removed

@gaaclarke
Copy link
Member Author

I talked to @stuartmorgan offline. The criteria for which he'd feel comfortable making any change are undefined since a risk is believed to exist but he has no way to prove a change presents a problem or not because the problem is not fully understood. Anything short of a verbatim copy from chromium code and its dependencies presents an unacceptable risk.

The one thing he is sure about is that the definitions should be the same across TU's so we should avoid ifdefs and the one thing I'm sure about is that we should have the annotations like __unsafe_unretained. That's where we are now.

Given what has been discussed I think there should be a unit test compiled and run with ARC and one for manual reference counting to make sure we get the annotations correct. That may be moot if stuart doesn't feel comfortable even with that assurance.

@stuartmorgan-g
Copy link
Contributor

The criteria for which he'd feel comfortable making any change are undefined since a risk is believed to exist but he has no way to prove a change presents a problem or not because the problem is not fully understood. Anything short of a verbatim copy from chromium code and its dependencies presents an unacceptable risk.

This is not an accurate summary of my position.

Given that after several rounds of discussion we still don't seem to have reached a common understanding of the situation, I think the best option is to continue with another reviewer.

@gaaclarke
Copy link
Member Author

@stuartmorgan Ok, since this isn't a priority right now I'll drop this back to a draft, implement the unit tests that run on ARC and non-ARC, maintain the policy of no-ifdefs to avoid your concern about ODR violations and solicit the review from an elder statesman.

@gaaclarke gaaclarke marked this pull request as draft May 19, 2020 18:11
Copy link

@dmaclach dmaclach left a comment

Choose a reason for hiding this comment

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

In general the ARC and mem management LGTM.

// We check for bad uses of scoped_nsobject and NSAutoreleasePool at compile
// time with a template specialization (see below).

class scoped_nsprotocol_arc_memory_management {

Choose a reason for hiding this comment

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

Been a while since I've done heavy C++, but can this just be a namespace?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep, it could be. The reason things are a laid out the way they are is to keep the code somewhat similar to the chrome implementation. I'll give it another style pass once I can verify that the generated machine code is fine.

@stuartmorgan-g
Copy link
Contributor

In general the ARC and mem management LGTM.

Well, that's what Rohit and I thought about the first broken version :)

Addressing a comment from earlier:

I think there should be a unit test compiled and run with ARC and one for manual reference counting to make sure we get the annotations correct.

Since that's how we figured out that the version we wrote had UB in the first place, I agree that this should be a requirement for landing any version of this change.

@dmaclach
Copy link

In general the ARC and mem management LGTM.

Well, that's what Rohit and I thought about the first broken version :)

I didn't actually see all of this background discussion until after I posted my comment. I was busy editing my previous post and then saw that you had already replied.

Addressing a comment from earlier:

I think there should be a unit test compiled and run with ARC and one for manual reference counting to make sure we get the annotations correct.

Since that's how we figured out that the version we wrote had UB in the first place, I agree that this should be a requirement for landing any version of this change.

Seems reasonable. As you mention it does not guarantee that ODR/UB problems don't exist, but as Aaron points out it gives us as good a bar as any of our other code, that is we wrote a test that executed the code paths that we were concerned about in the environments that we were concerned about, and it passed.

I'll be interested to see if Mark has any other thoughts.

@gaaclarke
Copy link
Member Author

I had @bdash look into this. He said there may be concerns with an ODR violation. He suspects that we might be able to get to a point where the annotations cause the compilation of this code with ARC and without ARC to be identical. If we could get to that point and show that they were identical there would be no possible ODR violation since whatever version of the machine code the loader grabbed, they would be identical. That gives this PR an avenue forward, I'll give it a look.

He also mentioned they use CFType in the past to work around this in webkit, but wasn't keen to recommend it as a first course of action. That worked since it was a way to get ARC to ignore your code and compile the same between ARC and MRC.

@bdash
Copy link

bdash commented Sep 14, 2020

To be explicit, the ODR concern is that when compiled under ARC vs MRC, the compiler will generate different machine code for methods that deal directly with the Obj-C pointer type (either id or NST in this templated code) unless special care is taken. In particular, assignments to a local variable or member variable of that type may bring with them retain count operations in ARC. Returning a value of that type from a function may also result in retain / autorelease operations.

It's up to the linker which of the many definitions of an inline function is selected, and there's no guarantee it'll select the ARC vs MRC version for any given member function, which may lead to leaks or over-releases if they end up being mixed.

The approach I suggested was to ensure that every use of NST (or id) as a bare pointer type declaring a member, parameter, or local variable was annotated with __unsafe_unretained, and that any method with a return type of NST (or id) be annotated as ns_returns_not_retained. This should be sufficient to prevent the compiler from emitting any retain / release / autorelease operations under ARC, and should result in equivalent machine code for code in ARC / MRC translation units that use this type. I believe this is the approach that Chromium's equivalent type used, prior to the removal of its support for ARC.

@gaaclarke
Copy link
Member Author

Ok, I took a look. As the code stands today here is what the disassembly looks like for arc/mrc for the scoped_nsobject constructor:

main_arc`fml::scoped_nsobject<Foobar>::scoped_nsobject:
    0x100003c60 <+0>:   pushq  %rbp
    0x100003c61 <+1>:   movq   %rsp, %rbp
    0x100003c64 <+4>:   subq   $0x20, %rsp
    0x100003c68 <+8>:   movq   %rdi, -0x8(%rbp)
    0x100003c6c <+12>:  movq   $0x0, -0x10(%rbp)
    0x100003c74 <+20>:  leaq   -0x10(%rbp), %rdi
    0x100003c78 <+24>:  callq  0x100003ed0               ; symbol stub for: objc_storeStrong
    0x100003c7d <+29>:  movq   -0x8(%rbp), %rdi
->  0x100003c81 <+33>:  movq   -0x10(%rbp), %rsi
    0x100003c85 <+37>:  callq  0x100003d80               ; fml::scoped_nsobject<NSArray>::scoped_nsobject at scoped_nsobject.h:132
    0x100003c8a <+42>:  jmp    0x100003c8f               ; <+47> at scoped_nsobject.h
    0x100003c8f <+47>:  xorl   %eax, %eax
    0x100003c91 <+49>:  movl   %eax, %esi
    0x100003c93 <+51>:  leaq   -0x10(%rbp), %rcx
    0x100003c97 <+55>:  movq   %rcx, %rdi
    0x100003c9a <+58>:  callq  0x100003ed0               ; symbol stub for: objc_storeStrong
    0x100003c9f <+63>:  addq   $0x20, %rsp
    0x100003ca3 <+67>:  popq   %rbp
    0x100003ca4 <+68>:  retq   
    0x100003ca5 <+69>:  xorl   %ecx, %ecx
    0x100003ca7 <+71>:  movl   %ecx, %esi
    0x100003ca9 <+73>:  movq   %rax, -0x18(%rbp)
    0x100003cad <+77>:  movl   %edx, -0x1c(%rbp)
    0x100003cb0 <+80>:  leaq   -0x10(%rbp), %rax
    0x100003cb4 <+84>:  movq   %rax, %rdi
    0x100003cb7 <+87>:  callq  0x100003ed0               ; symbol stub for: objc_storeStrong
    0x100003cbc <+92>:  movq   -0x18(%rbp), %rdi
    0x100003cc0 <+96>:  callq  0x100003eac               ; symbol stub for: _Unwind_Resume
    0x100003cc5 <+101>: ud2    
    0x100003cc7 <+103>: nopw   (%rax,%rax)
main_mrc`fml::scoped_nsobject<Foobar>::scoped_nsobject:
    0x100003de0 <+0>:  pushq  %rbp
    0x100003de1 <+1>:  movq   %rsp, %rbp
    0x100003de4 <+4>:  subq   $0x10, %rsp
    0x100003de8 <+8>:  movq   %rdi, -0x8(%rbp)
    0x100003dec <+12>: movq   %rsi, -0x10(%rbp)
    0x100003df0 <+16>: movq   -0x8(%rbp), %rdi
->  0x100003df4 <+20>: movq   -0x10(%rbp), %rsi
    0x100003df8 <+24>: callq  0x100003e90               ; fml::scoped_nsobject<NSArray>::scoped_nsobject at scoped_nsobject.h:132
    0x100003dfd <+29>: addq   $0x10, %rsp
    0x100003e01 <+33>: popq   %rbp
    0x100003e02 <+34>: retq   
    0x100003e03 <+35>: nopw   %cs:(%rax,%rax)
    0x100003e0d <+45>: nopl   (%rax)

I added the following patch:

-  explicit scoped_nsobject(NST* object = Memory::InvalidValue())
+  explicit scoped_nsobject(__unsafe_unretained NST* object = Memory::InvalidValue())
       : scoped_nsprotocol<NST*>(object) {}

The disassembly for the arc version of the constructor after this change is identical. I'll ponder this a bit more. I think just making scoped_nsobject just a wrapper around scoped_nsobject could avoid this problem.

@gaaclarke
Copy link
Member Author

If anyone else wants to play around with it I posted my testbed: https://github.com/gaaclarke/scoped_nsobject_test

@gaaclarke
Copy link
Member Author

Notice that the symbols for instantiations of scoped_nsprotocol can't collide between arc and mrc because the fact it is an arc covered class is codified in the type (notice Foobar* __strong):

aaclarke-macbookpro2:scoped_nsobject_test aaclarke$ nm main_arc | c++filt 
0000000100003b50 unsigned short -[Foobar dealloc]
0000000100003efc short GCC_except_table1
0000000100003f0c short GCC_except_table2
0000000100003f1c short GCC_except_table5
0000000100003f30 short GCC_except_table7
0000000100008108 S _OBJC_CLASS_$_Foobar
                 U _OBJC_CLASS_$_NSObject
0000000100008130 S _OBJC_METACLASS_$_Foobar
                 U _OBJC_METACLASS_$_NSObject
0000000100008088 short __OBJC_$_INSTANCE_METHODS_Foobar
00000001000080a8 short __OBJC_CLASS_RO_$_Foobar
0000000100008040 short __OBJC_METACLASS_RO_$_Foobar
                 U __Unwind_Resume
0000000100008160 bool s_didDealloc
0000000100003c20 unsigned short fml::scoped_nsobject<Foobar>::scoped_nsobject(Foobar*)
0000000100003d40 unsigned short fml::scoped_nsobject<Foobar>::scoped_nsobject(Foobar*)
0000000100003c90 unsigned short fml::scoped_nsobject<Foobar>::~scoped_nsobject()
0000000100003cb0 unsigned short fml::scoped_nsobject<Foobar>::~scoped_nsobject()
0000000100003db0 unsigned short fml::scoped_nsprotocol<Foobar* __strong>::scoped_nsprotocol(Foobar*)
0000000100003cd0 unsigned short fml::scoped_nsprotocol<Foobar* __strong>::~scoped_nsprotocol()
0000000100003e30 T fml::scoped_nsprotocol_arc_memory_management::Autorelease(objc_object*)
0000000100003e10 T fml::scoped_nsprotocol_arc_memory_management::Retain(objc_object*)
0000000100003e50 T fml::scoped_nsprotocol_arc_memory_management::Release(objc_object*)
                 U std::terminate()
0000000100003d30 unsigned short ___clang_call_terminate
                 U ___cxa_begin_catch
                 U ___gxx_personality_v0
0000000100008158 double __dyld_private
0000000100000000 T __mh_execute_header
                 U __objc_empty_cache
0000000100003b90 T _main
                 U _objc_alloc_init
                 U _objc_autorelease
                 U _objc_msgSendSuper2
                 U _objc_release
                 U _objc_retain
                 U _objc_storeStrong
                 U _printf
                 U dyld_stub_binder

That means that there can't be an ODR violation for scoped_nsprotocol because they map to 2 different symbols depending on if a type is covered by arc or not.

The problem is that the way scoped_nsobject is implemented it scrapes off that type information here:

class scoped_nsobject : public scoped_nsprotocol<NST*>

If we can find a way to preserve that type information we wouldn't have a problem. That might mean having users say fml::scoped_nsobject<Foobar*> instead of fml::scoped_nsobject<Foobar>.

@bdash
Copy link

bdash commented Sep 15, 2020

Ugh. From the generated code it looks like Clang is allocating a stack slot to hold the object, zeroing it, then using objc_storeStrong to assign the object parameter to it, before loading the value from that stack slot and passing it to the base class constructor. It then uses objc_storeStrong to clear the stack slot. This all has the effect of retaining / releasing the passed-in object for the duration of the constructor call. I'm a little surprised that it's doing that considering both the value being passed in and the destination parameter are __unsafe_unretained.

You're right that if the pointer type, rather than the class type, is used as the template parameter then you get different mangling which may be sufficient to avoid ODR problems. Relying on that would require changing the way these smart pointers are used, but it seems like it may be a safer approach than trying to coax Clang into generating exactly what you're after.

maintains special typenames for classes being maintained by ARC.

This avoids an ODR violation where ARC / MRC machine code is randomly
grabbed incorrectly for 2 equal symbols.  Now ARC / MRC versions of
templates have unique symbols.
@gaaclarke gaaclarke marked this pull request as ready for review September 16, 2020 01:28
@flutter-dashboard
Copy link

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

@gaaclarke
Copy link
Member Author

Ok, I pushed a commit where switched scoped_nsobject so that it doesn't truncate the ARC specific types, this will make the symbols for the same "type" be different symbols so there isn't a chance for ODR violation (example Foobar* vs Foobar* __strong). Thanks @bdash for your help. @stuartmorgan please give it a look and let me know if you have any concerns.

static void Release(id object) { [object release]; }
static id InvalidValue() { return nil; }
};
#endif
Copy link

Choose a reason for hiding this comment

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

I believe this introduces another ODR violation. You again have symbols with identical names where the definitions are different in different translation units (ARC vs non-ARC). In this case, the violation is even clearer since the definitions consist of different tokens, rather than the earlier iteration where the same tokens led to different machine code due to ARC.

Copy link
Member Author

Choose a reason for hiding this comment

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

What symbols do you believe are the same between the ARC and non-ARC TUs? If fml::scoped_nsobject<Foobar*> foo; is compiled with ARC we should have a fml::scoped_nsobject<Foober* __strong>::scoped_nsobject() symbol and if it is compiled without ARC it will be a fml::scoped_nsobject<Foobar*>::scoped_nsobject() symbol.

I don't think there is a way, and it definitely isn't easy, to create a fml::scoped_nsobject<Foobar*>::scoped_nsobject(); when compiling with ARC.

Copy link

Choose a reason for hiding this comment

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

The symbols in the scoped_nsprotocol_memory_management class.

Copy link

Choose a reason for hiding this comment

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

Though I suppose they may also end up with different mangling due to taking parameters / returning values that are Obj-C types? I'm not clear if the difference in mangling is specific to template type parameters, or if it extends to parameters and return types as well.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ahh good catch, thanks! I addressed it by making scoped_nsprotocol_memory_management templated around the NST.

Copy link
Member Author

Choose a reason for hiding this comment

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

(at the cost of a slightly larger binary)

Copy link
Contributor

Choose a reason for hiding this comment

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

What symbols do you believe are the same between the ARC and non-ARC TUs? If fml::scoped_nsobject<Foobar*> foo; is compiled with ARC we should have a fml::scoped_nsobject<Foober* __strong>::scoped_nsobject() symbol and if it is compiled without ARC it will be a fml::scoped_nsobject<Foobar*>::scoped_nsobject() symbol.

If scoped_nsobject evaluates to different classes in ARC and non-ARC, then you're just pushing the ODR violation up a level; any C++ class with a scoped_nsobject ivar that's included from both ARC and non-ARC code (which is the case this whole effort is intended to allow, so not an edge case) is now violating the ODR, because there are two different versions of that class.

Copy link
Member Author

Choose a reason for hiding this comment

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

@stuartmorgan I think you're mistaken. If you have:

class Foo {
 public:
  Foo();
 private:
  fml::scoped_nsobject<NSArray*> array_;
};

You will only get 1 version of Foo::Foo(), it will be the one that gets compiled from foo.mm. It will reference fml::scoped_nsobject<NSArray*>::scoped_nsobject() or fml::scoped_nsobject<NSArray* __strong>::scoped_nsobject() depending on if it was compiled with ARC or not. Were you thinking of a different setup?

Copy link

Choose a reason for hiding this comment

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

Stuart's correct. Consider what happens if an inline function in a header file interacts with a scoped_nsobject<NSArray *>. It'll emit references to different symbols depending on whether it is imported into an ARC or MRC translation unit. That is an ODR violation since the same function now has different definitions in different translation units.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ahh yea. In the above example ~Foo() would be problematic, right?

One possible way forward would be to annotate the code correctly and prove that the machine code is equivalent (if not equal) with unit tests. That sounds like a fair bit of work and has a risk of not panning out.

Another would be do so something similar to what @bdash mentioned where we use void* or CFTypeRef to bypass ARC's code generation to make the machine code equivalent.

I'm loath to take any more of your time, I thought I could hammer this out quickly. We can live without ARC, it's just a shame to live without it. I've spent a lot of time fixing memory bugs in our objc code and we have a whole lot more to write while we catch up with unit tests.

@gaaclarke
Copy link
Member Author

gaaclarke commented Sep 18, 2020

I'm closing this PR. I think the best approach would be to delete scoped_nsobjects, which is a better alternative than the more complicated task of migrating them to the pimpl pattern and enforcing they aren't used in headers.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants