Project

General

Profile

Bug #5296

output parameters are cleared if an argument is another function call

Added by Constantin Asofiei about 3 years ago. Updated almost 3 years ago.

Status:
Closed
Priority:
Normal
Target version:
-
Start date:
Due date:
% Done:

100%

billable:
No
vendor_id:
GCD
case_num:

History

#2 Updated by Constantin Asofiei about 3 years ago

If a call has output/input-output parameters, and also a function call, then the output argument registration is lost! See this:

def var ch as char.

function func0 returns int(input-output v as char, input i as int).
v = string(i).
end.

function func1 returns int.
   return 10.
end.

func0(input-output ch, func1()).

message ch.

This is because of the BlockDefinition.outputParmAssigner, which assumes that the very next top-level block call (function, method) will consume it. This is because FWD registers these 'assigners' in the current block, and this works for simple calls, but when the argument has another call, is not OK.

We need to differentiate the arguments for each call, even if they are 'nested'.

I don't see this solved without lambda expressions and delaying the evaluation of the arguments for the nested call.

Conversion would emit like func0(wrap(ch, true), evaluate(() -> func1()));, and there is a BlockManager.evaluate method like:

   public static <T> T evaluate(Supplier<T> call)
   {
      OutputParameterAssigner opa = TransactionManager.deregisterOutputParameterAssigner();

      try
      {
         return call.get();
      }
      finally
      {
         TransactionManager.registerOutputParameterAssigner(opa);
      }
   }

If someone has a better idea, please let me know.

#3 Updated by Constantin Asofiei about 3 years ago

  • Status changed from New to WIP
  • % Done changed from 0 to 90

The fix is in 3821c rev 12345

An improvement would be to emit evaluate(() -> ...) only if there are no output/input-output arguments before this call, but I'm not sure how easy this can be done.

#4 Updated by Constantin Asofiei almost 3 years ago

The current changes are too aggressive, I need to limit them for only if there are OUTPUT/INPUT-OUTPUT parameters on the parent's call.

#5 Updated by Greg Shah almost 3 years ago

  • Start date deleted (04/28/2021)

Code Review Task Branch 3821c Revision 12345

The changes themselves should do what you intend.

The biggest issue I see is that the resulting code is hard to understand. The extra encoding required for the arguments is confusing and quite disconnected from the purpose. This means it is going to be the source of bugs and problems over the lifetime of this solution.

As an alternative to the current approach, can we use an init() block and have a registration call at the beginning which tells the BlockManager to process/register all output or input-output parameters. This is the moment when we know the correct block is open and we can simply register it directly. This would replace all other registration and the dereg/reg of the evaluate().

#6 Updated by Constantin Asofiei almost 3 years ago

It doesn't work to move this registration at init(), because the reference needs to change - and in lambda we can't do this.

We need to emit another var defined before the top-level block (as we do for INPUT), and the registration needs to be done there, something like, if the parameter is named as integer i_ then there is a integer i = wrap(i_, true)); defined before the top-level block.

And the changes are pretty intrusive, as we need to remove the wrap at the caller; plus, I think there are some runtime places where wrap is called internally, when processing the argument - these places need to be reviewed and changed.

But I agree, evaluate doesn't a good choice, as it complicates the call a lot.

#7 Updated by Greg Shah almost 3 years ago

Since we are making this change, we could modify the wrap() to be outputParm() or inputOututParm() and remove the boolean parameter. I think it would read better since it would be more directly associated with the purpose of the call.

#8 Updated by Constantin Asofiei almost 3 years ago

Greg, I remembered the reason why this wrap was needed and the registration was done at the caller, and not the definition: the field and OO property case. For these, the callee doesn't have any knowledge, and the caller must perform the 'wrapping' so that the field/OO property is assigned properly when the call finishes.

#9 Updated by Constantin Asofiei almost 3 years ago

I think the wrap methods for the field, property and other cases should return a BDT anon-class which overrides a getAssigner method, to return the real VariableAssigner, etc - and this will be used by the method implementation to get the assigner and register it when its top-level scope opens. The only ugly part is that I'll need to hard-code all BDT sub-classes, and create a new anon-class which overrides the getAssigner to return the proper value (the reason is to not add another field to BaseDataType).

More, the extent parameters are also using the same mechanism to register at the caller first, and then pushed at the callee, when that opens its top-level scope. For these, as the conversions explicitly emits InputOutputExtentParameter (and others) anon-class instances at the caller, I think is safe to save the assigner at this instance and register it when the callee executes.

For all cases, the Java method definition will need to emit something like: BDT param = TypeFactory.initOutput(_param) which will take care of the registration.

#10 Updated by Constantin Asofiei almost 3 years ago

So, something I didn't consider before - the hand-written legacy OO implementations in FWD.

Now, the assigner gets created and registered at the method call execution - there are TypeFactory.initOutput and initInputOutput APIs which are emitted for all output/input-output parameters (and for extent parameters, too) - there is no more registration of these assigners at the parameter evaluation in Java, all is done in the Java method at the actual call execution. But this requires changing all p2j.oo code to this new approach.

The changes are stable and I think complete (I've checked the REST/SOAP/WebHandler SoapUI tests), but the failure is because of the p2j.oo code which needs to be updated to this new approach.

I need to analyze the LegacyParameter annotations in p2j.oo and build a tool which automatically emits the TypeFactory APIs. I think is worth merging 4384k to 3821c at the time when I have this tool stable.

#11 Updated by Constantin Asofiei almost 3 years ago

The good news - the entire list of output/input-output parameters in p2j.oo is short, so no need to automate this; is feasible (and faster) to be done by hand.

com.goldencode.p2j.oo.core.collections.AbstractTtcollection.toTable OUTPUT p1                                                                                                                                                                                                      
com.goldencode.p2j.oo.core.collections.Ilist.toTable OUTPUT p1                                                                                                                                                                                                                     
com.goldencode.p2j.oo.core.collections.LegacyCollection.toTable OUTPUT p1                                                                                                                                                                                                          
com.goldencode.p2j.oo.core.collections.MapBackedCollection.toTable OUTPUT p1                                                                                                                                                                                                       
com.goldencode.p2j.oo.database.TempTableInfo.getIndexInfoById OUTPUT index-name                                                                                                                                                                                                    
com.goldencode.p2j.oo.database.TempTableInfo.getIndexInfoById OUTPUT index-name                                                                                                                                                                                                    
com.goldencode.p2j.oo.database.TempTableInfo.getIndexInfoById OUTPUT proc-name                                                                                                                                                                                                     
com.goldencode.p2j.oo.database.TempTableInfo.getIndexInfoById OUTPUT proc-name                                                                                                                                                                                                     
com.goldencode.p2j.oo.database.TempTableInfo.getIndexInfoById OUTPUT table-handle                                                                                                                                                                                                  
com.goldencode.p2j.oo.database.TempTableInfo.getTableInfoById OUTPUT proc-name                                                                                                                                                                                                     
com.goldencode.p2j.oo.database.TempTableInfo.getTableInfoById OUTPUT proc-name                                                                                                                                                                                                     
com.goldencode.p2j.oo.database.TempTableInfo.getTableInfoById OUTPUT table-handle                                                                                                                                                                                                  
com.goldencode.p2j.oo.database.TempTableInfo.getTableInfoById OUTPUT table-name                                                                                                                                                                                                    
com.goldencode.p2j.oo.database.TempTableInfo.getTableInfoByPosition OUTPUT proc-name                                                                                                                                                                                               
com.goldencode.p2j.oo.database.TempTableInfo.getTableInfoByPosition OUTPUT proc-name                                                                                                                                                                                               
com.goldencode.p2j.oo.database.TempTableInfo.getTableInfoByPosition OUTPUT table-handle                                                                                                                                                                                            
com.goldencode.p2j.oo.database.TempTableInfo.getTableInfoByPosition OUTPUT table-name                                                                                                                                                                                              
com.goldencode.p2j.oo.io.FileInputStream.read OUTPUT target                                                                                                                                                                                                                        
com.goldencode.p2j.oo.io.FileInputStream.read OUTPUT target                                                                                                                                                                                                                        
com.goldencode.p2j.oo.io.InputStream.read OUTPUT target                                                                                                                                                                                                                            
com.goldencode.p2j.oo.io.InputStream.read OUTPUT target                                                                                                                                                                                                                            
com.goldencode.p2j.oo.json.objectmodel.JsonConstruct.write INPUT-OUTPUT target                                                                                                                                                                                                     
com.goldencode.p2j.oo.json.objectmodel.JsonConstruct.write INPUT-OUTPUT target                                                                                                                                                                                                     
com.goldencode.p2j.oo.json.objectmodel.JsonConstruct.write INPUT-OUTPUT target                                                                                                                                                                                                     
com.goldencode.p2j.oo.json.objectmodel.JsonConstruct.write OUTPUT target                                                                                                                                                                                                           
com.goldencode.p2j.oo.json.objectmodel.JsonConstruct.write OUTPUT target                                                                                                                                                                                                           
com.goldencode.p2j.oo.json.objectmodel.JsonConstruct.write OUTPUT target                                                                                                                                                                                                           
com.goldencode.p2j.oo.net.http.AuthenticatedRequest.getAuthenticationCallbacks OUTPUT extpoListeners                                                                                                                                                                               
com.goldencode.p2j.oo.net.http.CookieJarDecorator.getCookies OUTPUT poCookies                                                                                                                                                                                                      
com.goldencode.p2j.oo.net.http.CookieJar.getCookies OUTPUT poCookies                                                                                                                                                                                                               
com.goldencode.p2j.oo.net.http.filter.auth.AuthenticationRequestFilter.write INPUT-OUTPUT pcData                                                                                                                                                                                   
com.goldencode.p2j.oo.net.http.filter.auth.AuthenticationRequestFilter.write INPUT-OUTPUT pcData                                                                                                                                                                                   
com.goldencode.p2j.oo.net.http.filter.header.SetCookieSetHeaderFilter.write INPUT-OUTPUT pcData                                                                                                                                                                                    
com.goldencode.p2j.oo.net.http.filter.header.SetCookieSetHeaderFilter.write INPUT-OUTPUT pcData
com.goldencode.p2j.oo.net.http.filter.header.TransferEncodingSetHeaderFilter.write INPUT-OUTPUT pcData
com.goldencode.p2j.oo.net.http.filter.header.TransferEncodingSetHeaderFilter.write INPUT-OUTPUT pcData
com.goldencode.p2j.oo.net.http.filter.payload.ClientSocketResponseWriter.write INPUT-OUTPUT pcData
com.goldencode.p2j.oo.net.http.filter.payload.ClientSocketResponseWriter.write INPUT-OUTPUT pcData
com.goldencode.p2j.oo.net.http.HttpHeaderCollection.getAll OUTPUT p1
com.goldencode.p2j.oo.net.http.HttpMessage.getCookies OUTPUT p1
com.goldencode.p2j.oo.net.http.HttpMessage.getHeaders OUTPUT p1
com.goldencode.p2j.oo.net.http.HttpRequestDecorator.getCookies OUTPUT extpoCookies
com.goldencode.p2j.oo.net.http.HttpRequestDecorator.getHeaders OUTPUT extpoHeader
com.goldencode.p2j.oo.net.http.HttpResponseDecorator.getCookies OUTPUT extpoCookies
com.goldencode.p2j.oo.net.http.HttpResponseDecorator.getHeaders OUTPUT extpoHeader
com.goldencode.p2j.oo.net.http.IAuthenticatedRequest.getAuthenticationCallbacks OUTPUT poListener
com.goldencode.p2j.oo.net.http.ICookieJar.getCookies OUTPUT poCookies
com.goldencode.p2j.oo.net.http.IhttpMessage.getCookies OUTPUT poCookies
com.goldencode.p2j.oo.net.http.IhttpMessage.getHeaders OUTPUT poHeaders
com.goldencode.p2j.oo.net.http.IHttpMessageWriter.write INPUT-OUTPUT pcData
com.goldencode.p2j.oo.net.http.IHttpMessageWriter.write INPUT-OUTPUT pcData
com.goldencode.p2j.oo.net.Uri.getPathSegments OUTPUT p1
com.goldencode.p2j.oo.net.Uri.getQueryNames OUTPUT np1
com.goldencode.p2j.oo.web.RemoteWebRequest.getCookies OUTPUT poCookies
com.goldencode.p2j.oo.web.RemoteWebRequest.getHeaders OUTPUT poHeaders

#12 Updated by Constantin Asofiei almost 3 years ago

Created task branch 5296a from 3821c rev 12518.

#13 Updated by Constantin Asofiei almost 3 years ago

The changes are in 5296a rev 12519. Hotel GUI works OK (conversion and runtime). Please review.

The fix list is this:
  • Reworked INPUT/INPUT-OUTPUT parameters to a new approach, where they are explicitly initialized at the method's execution, and not at the caller's arguments.
  • 'wrap' arguments are emitted on in special cases (fields, properties, type mismatch) and the assigner registration is delayed until the variable is initialized via 'initOutput' or 'initInputOutput'.
  • Updated the 'p2j.oo' code to use the new approach for OUTPUT/INPUT-OUTPUT arguments.
  • Fixed conversion and runtime issues with extent properties used as output/input-output arguments.
  • Fixed conversion and runtime issues with CALL parameters (fields, properties, extent or not).
  • Fixed a NPE in LobCopy, when the target has no codepage set.
  • Utils.invoke throws a NPE if the instance is null and the target is an instance method.
  • A DYNAMIC-FUNCTION/FUNCTION call must raise an ERROR condition only and only if the failure is from a remote appserver agent call, and not a call from within the appserver agent execution.

#14 Updated by Greg Shah almost 3 years ago

Code Review Task Branch 5296a Revision 12519

Overall, the update looks reasonable. Unfortunately, my lack of knowledge of the output/input-output parameter processing limits my proper understanding of the changes.

Why is the setInMethod() bracketing needed inside a function definition. Doesn't that only matter inside a class definition (where a function cannot be defined)?

#15 Updated by Constantin Asofiei almost 3 years ago

Greg Shah wrote:

Overall, the update looks reasonable. Unfortunately, my lack of knowledge of the output/input-output parameter processing limits my proper understanding of the changes.

The changes are kind of weird. I'm using the anonymous classes in OutputParameter.createVariable to create a special var which only holds in the getAssigner's returned lambda the actual assigner instance. This is kept 'in memory' while parameters are processed by ControlFlowOps, and the assigner is actually created only when i.e. TypeFactory.initOutput is called. Also, this anon instance must be 'translated back' to a non-anonymous instance (normal BDT type). I can't leave the anonymous class instance behind because this will give all kinds of problems (there is no default constructor for it, there are places which do bdt.getClass() == integer.class for example, map lookup with BDT.class keys, etc) and all these will fail if the anon instance is used.

Otherwise, beside moving the assigner initialization at the method execution, the registration works the same: the assigner is registered in the 'current scope' OutputParameterAssigner (before the actual BlockManager API call), and this will be processed as before. No changes there. The big difference is that this registration ensures that all arguments are resolved, so is safe to use this approach, as no other call can interfere with the OutputParameterAssigner for the current scope.

Why is the setInMethod() bracketing needed inside a function definition. Doesn't that only matter inside a class definition (where a function cannot be defined)?

A function can be defined IN handle in a class.

The change is needed because the function's defined parameters can have the same name as the class members (variables/properties) - and the parser previously decided to look at the class members, and it set the 'tempidx' wrong. More, at the function call, things got messed up when trying to resolve the argument's type, as the definition was linking to i.e. class properties instead of the function's parameter.

In other words, this solves if there are name collisions between function parameters and class variables/properties.

#16 Updated by Greg Shah almost 3 years ago

Thanks, those explanations are helpful.

#17 Updated by Constantin Asofiei almost 3 years ago

Rebased 5296a from 3821c rev 12530 - new revision 12534.

In 5296a rev 12533 and 12534 I've fixed some other issues found during testing.

I think is safe to merge to 3821c at this time (all projects will require full conversion).

#19 Updated by Greg Shah almost 3 years ago

Code Review Task Branch 5296a Revisions 12533-12534

I only see one minor issue. In AbstractParameter.postProcessOutput(), I think there is some dead code.

         if (origAssigner.outputValue instanceof ObjectVar)
         {
            origAssigner.outputValue = new ObjectVar((ObjectVar) origAssigner.outputValue);
         }
         else
         {
            Class<?> cls = origAssigner.outputValue instanceof ObjectVar       <----- This is always going to be false.
                              ? ObjectVar.class 
                              : BaseDataType.fromTypeName(origAssigner.outputValue.getTypeName());

#20 Updated by Greg Shah almost 3 years ago

You can merge 5296a to 3821c.

#21 Updated by Constantin Asofiei almost 3 years ago

I've fixed the dead code issue and merged 5296a/12535 to 3821c 12531.

5296a was archived.

#22 Updated by Greg Shah almost 3 years ago

Is there anything remaining to do in this task?

#23 Updated by Constantin Asofiei almost 3 years ago

  • % Done changed from 90 to 100

Greg Shah wrote:

Is there anything remaining to do in this task?

No, it can be closed.

#24 Updated by Greg Shah almost 3 years ago

  • Status changed from WIP to Closed
  • Assignee set to Constantin Asofiei

Also available in: Atom PDF