Bug #5296
output parameters are cleared if an argument is another function call
100%
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