When registering an OLE server without appropriate (admin) rights, TOcRegistrar throws "TRegKey::TRegKey: Init failed", even if the "-QuietReg" option is supplied in the command line, or the preselectedOptions parameter of the TOcRegistrar::TOcRegistrar() call has the amNoRegValidate flag set.
PS. Also see related issue "TOcRegistrar constructor throws exception when registry access fails (regression in 6.40)" [bugs:#376].
Bugs: #376
Discussion: State of OCFNext — time to discontinue?
Wiki: OWLNext_Stable_Releases
This issue is a regression caused by [r4865], which introduced new exception behaviour for TRegKey constructors, in line with modern C++ coding style. See [feature-requests:#151].
Proposed resolution: OCF should be updated to handle exceptions in TRegKey.
Related
Commit: [r4865]
Feature Requests:
#151Last edit: Vidar Hasfjord 2021-06-11
Hi Erwin,
I've had a brief look at the OCF code, and it looks to me the problem is in the catch block in TAppDescriptor::RegisterServer:
I guess it is the attempt to put that "OcErr_RegServer" value (presumably as an error indicator) into the registry here that causes an exception which escapes. Is this what you see?
If so, the suppression of this exception may be enough to solve the issue:
Please test.
However, there may be other code in OCF that assumes TRegKey will not throw, so a broader review may be needed.
Hi Vidar,
you have correctly identified the source of the exception.
The solution should work, but doesn't because the amQuietReg Flag is not set in 'Options' despite "-QuietReg" is given in the command line. It is set, however, if I put it into the 'preselectedOptions' parameter of the TOcRegistrar constructor. Strange.
In addition: Shouldn't 'amNoRegValidate' also suppress exceptions?
Hi Erwin,
Thanks for the feedback.
Note that options are processed in sequence in TAppDescriptor::ProcessCmdLine, so "-QuietReg" needs to be passed before "-RegServer".
Ideally, this should be made more robust, by allowing any order.
As fas as I can see amNoRegValidate is only used at the end of TAppDescriptor::ProcessCmdLine, where if this option is set, then "normal registry update" is skipped:
RegisterServer is only called within ProcessCmdLine — in this line, or as a response to the amRegServer option (processed earlier in the same function) — so the question then becomes whether the option amNoRegValidate should disable exceptions if amRegServer is given (and if so, it would have to be specified on the command line before the latter, to be effective).
Also note that, unlike RegisterServer, TAppDescriptor::UnregisterServer has no exception handling, so it may now throw TXRegistry, regardless of the given options (amQuietReg and amNoRegValidate).
The documentation is thin, so I am not sure what the intended behaviour is. Feel free to take control and make sense of it!
Diff:
Related
Bugs:
#376Ok, in reverse order "-QuietReg" is recognized.
Absolutely. It's REALLY unusual to be forced to specify a detail-option before the option itself.
You can allow any order simply by processing the command line in two passes, I would think: The first pass records all the options, while the second acts on them.
You can then also do some sanity checking. For example, specifying more than one of options "RegServer", "UnregServer" and "NoRegValidate" should be an error, I think. If I understand the code correctly, then "NoRegValidate" is intended to be used only to suppress the default call to RegisterServer (the "normal registry update", as the code comment says) when none of options "RegServer" and "UnregServer" are given.
Right. Do you know whether TAppDescriptor::ProcessCmdLine() is called from other places too?
In case someone likes to unregister and register again in one run, this possibility might be desired. So I wouldn't like to risk too much if it isn't not really worth it.
Last edit: Erwin Lotter 2021-06-13
Hi Erwin,
Here is a patch that rewrites TAppDescriptor::ProcessCmdLine to allow any order of "QuietReg" and other flag options. It also detects if multiple registry options have been specified and warns (should perhaps generate an error?). And I have added documentation and code comments.
Beware: Not tested!
PS. By the way, why is 0 passed for the language ID in the call to RegisterServer at the end of the function? Why isn't AppLang passed, like for the processed options?
Edit: Patch removed. See later posts for later patch.
Last edit: Vidar Hasfjord 2021-06-15
I don't think that will work, since the processing happens in the order specified on the command line (regardless of lookup table order). My patch delays the actions until the whole command line has been parsed. It also delays the modification of the "cmdLine" parameter, in case there are exceptions.
Using "Find All References" and "Find All" in Visual Studio, I see it is called from TAppDescriptor constructors, as well as from TRegistrar::ProcessCmdLine. The latter is called from DllRegisterCommand, which in turn is called from DllRegisterServer and DllUnregisterServer.
Note: My patch has an issue. The AppLang argument passed to the action functions is not updated by the "Language" option, since the action functions and their arguments are bound before the actions are performed.Edit: Disregard the note above. The AppLang argument is accessed through the "this" pointer, so it will not be bound until the actual call of the action function, i.e. it will use the value at the time of the call.
Note that order of the "Language" option still matters: The "Language" option will have to be passed before "RegServer", for the latter to use the correct language ID. Perhaps the actions should be sorted, with the language option first, if present.
Why is AppLang passed at all to RegisterServer, UnregisterServer and MakeTypeLib? After all, it is a member, and these functions are only called from ProcessCmdLine (nowhere else). Even SetLangId has "prevLang" as a dummy parameter to allow AppLang to be passed for no reason.
Last edit: Vidar Hasfjord 2021-06-13
Hi Erwin,
Here is an alternative patch that addresses the strange API issues mentioned in my last post by removing superfluous parameters. It also allows the "Language" option to be specified in any order on the command line.
Beware: Not tested!
Edit: Added further documentation and code comments. Also made use of SetOption and IsOptionSet, rather than direct bit fiddling.
Edit: Patch file removed. See later posts for full patch.
Last edit: Vidar Hasfjord 2021-06-15
Can I apply this patch without having the source as a git repo? (I only have the download from OWLMaker.)
The patch was created with TortoiseSVN against [r5511], so I recommend creating a Subversion working copy (see our installation guide), and apply it using Subversion tools (TortoiseSVN). But you may be able to apply it using other patch tools, or even manually by hand — it isn't very complex.
Related
Commit: [r5511]
Ok, I can give -quietreg before and after -regserver to suppress the exception. However, the server does not shut down after registration as it should.
"-quietreg -unregserver" throws "TRegKey::GetSubKey: RegOpenKeyEx failed"
"-typelib xyz.tlb" crashes in MakeTypeLib() in line
TRegKey::GetClassesRoot().DeleteKey(_T("OcErr_Typelib"));Exception thrown at...: Access violation reading location 0x0000001C.
Hi Erwin, good work on the testing, and thanks for the feedback!
It looks like you have identified key spots in the code that previously failed silently, but now will need proper error/exception handling. Hopefully, they should be easy to patch.
To guard against access denial exceptions escaping, TAppDescriptor::MakeTypeLib and UnregisterServer will need a catch block similar to RegisterServer it seems. Further error handling may be needed where the code previously may have just failed silently and proceeded as if nothing happened.
Ideally, some of the code that should still work fine, could be improved as well. For example, the code in UnregisterServer will create keys if they do not exist, just to try to delete their subkeys, which makes no sense. For example:
This code will create "HKEY_CLASSES_ROOT\TypeLib", if it does not exist, after which it then tries to delete a non-existent subkey. This does no harm, but it is doing superfluous work by modifying the registry unnecessarily. And it isn't very clear what the code does in case the "TypeLib" key does not exist. A better coding style may be:
Note that, if the subkey does not exist, DeleteKey returns an error code here, which is ignored. It does not throw exceptions. However, an even clearer coding style, would be to write:
This code would work, even if DeleteKey at some point should be modernised to use exceptions for its error reporting.
Last edit: Vidar Hasfjord 2021-06-14
Hi Erwin,
Here is another patch (applicable to trunk [r5511]), that now implements similar exception handling for RegisterServer, UnregisterServer and MakeTypeLib, which are the action functions called by TAppDescriptor::ParseCmdLine. They now all handle TXBase exceptions the same way, suppressing them if the flag amQuietReg is set, as discussed.
I have also slightly rewritten UnregisterServer, as discussed earlier, so that the registry manipulating code is clear and exception safe for the future, while no longer creating non-existent keys unnecessarily.
Documentation has also been added for these functions, with a more complete comment on exceptions and the effect of the flag amQuietReg.
Beware: Not tested!
Note: The attached patch contains complete changes since [r5511]. The above code just shows UnregisterServer and MakeTypeLib.
Related
Commit: [r5511]
Last edit: Vidar Hasfjord 2021-06-14
Off-topic challenge: Using std::filesystem::path, rewrite the following nonsense in MakeTypeLib, and document what it is supposed to do:
With the new patch "-unregserver" behaves like "-regserver": "-quietreg" is accepted but the server does not shut down after reg update.
The crash with "-typelib" does not occur if the call of WriteTypeLibrary() - i.e. the line before the exception line - is commented out. Looks like stack/memory corruption in WriteTypeLibrary().
Hi Erwin,
Sounds like you have identified other issues deeper within OCF. To pinpoint the code that throws exceptions, be sure to turn on break on exceptions in the debugger. In Visual Studio, go to Debug | Windows | Exception Settings, then select C++ Exceptions | "All C++ Exceptions not in this list".
The code probably needs to be fixed (made more robust) so that it makes sense in the presence of exceptions, i.e. leave the program in a valid state.
Does your test code work fine without QuietReg when you run with administrator rights?
PS. On the topic of code simplification, I just played around with rewriting UnregisterServer to eliminate all this hard-to-read, explicit and brittle buffer handling:
By defining a simple overload of OcUnregisterClass, all that buffering can be removed:
The OcUnregisterClass overload is simply defined like this:
It simply takes the TRegItem parameter by const reference. This allows us to pass a temporary TRegItem in the call, thereby eliminating the need for any buffering (the temporaries used to initialise TRegItem stay alive to the end of the statement). The parameter is treated as optional, with the TRegItem default value being the empty state, thereby allowing us to optionally pass a proper value or an empty value in a single statement.
Note that the TRegItem arguments cannot be refactored into named variables without reintroducing buffering, since TRegItem is itself unbuffered (it has just two pointers as members). So the proper use of TRegItem is quite brittle.
Last edit: Vidar Hasfjord 2021-06-15
The exception settings didn't help on the -typelib exception, which might be related to the fact that my OLE server doesn't expose an automation interface..
Did you pinpoint the problem in WriteTypeLibrary?