Apache OpenOffice (AOO) Bugzilla – Issue 63614
IIF is fragile in StarBasic
Last modified: 2015-10-31 16:16:48 UTC
I have had macros that use IIF that will suddenly fail for no particular reason. Recently, I ran accross a great example of this. The macro is shown here: http://www.oooforum.org/forum/viewtopic.phtml?t=33910 Create a new module and add the macro (which I will append at the bottom of this post). The macro works just fine. Now, add a simple comment near the top. Change the top comment from one line to two REM making the use of calc function library within OOBasic easier REM Does this mess things up? Now, run the macro again and it will fail just before the IIF statement. Now, change the IIF statement from tempVec2 = DimArray( IIf( nCols = 0, 0, nCols - 1 ) ) to If nCols = 0 Then tempVec2 = DimArray( 0 ) Else tempVec2 = DimArray( nCols - 1 ) End If Now, the macro will work. REM ***** BASIC ***** Option Explicit REM making the use of calc function library within OOBasic easier Sub testCalcFunctions Dim a(1), b(1) a(0) = 1 : a(1) = 2 b(0) = 3 : b(1) = 4 MsgBox "Dot product is " & fnCalcFunction( "SUMPRODUCT", Array( a(), b() ) ) Dim x As Integer x = 5 MsgBox "Factorial of " & x & " is " & fnCalcFunction( "FACT", Array( x ) ) Dim tempArray(1,1) As Variant tempArray(0,0) = 0 : tempArray(0,1) = 1 tempArray(1,0) = 2 : tempArray(1,1) = 3 Dim funcResult() As Variant funcResult() = fnCalcFunction( "TRANSPOSE", Array( tempArray() ) ) MsgBox "Transpose of" & chr(13) & _ tempArray(0,0) & ", " & tempArray(0,1) & chr(13) & _ tempArray(1,0) & ", " & tempArray(1,1) & chr(13) & _ "is" & chr(13) & _ funcResult(0,0) & ", " & funcResult(0,1) & chr(13) & _ funcResult(1,0) & ", " & funcResult(1,1) MsgBox "Determinate is " & fnCalcFunction( "MDETERM", Array( tempArray() ) ) End Sub REM The following are trivially simple examples of user defined functions REM which can be used as formulas in a spreadsheet cell. Function fnAbs( a ) fnAbs() = fnCalcFunction( "ABS", Array( a ) ) End Function Function fnTranspose( a() ) fnTranspose() = fnCalcFunction( "TRANSPOSE", Array( a() ) ) End Function Function fnDot( a(), b() ) fnDot() = fnCalcFunction( "SUMPRODUCT", Array( a(), b() ) ) End Function REM ############################################################## REM ############################################################## REM ############################################################## REM REM The below functions provide the basic utilities for conveniently REM using calc functions (including array functions) within OOBasic. REM REM ############################################################## REM ############################################################## REM ############################################################## Function fnCalcFunction( sFunc As String, args() ) As Variant REM this function is just a utility function for making the REM FunctionAccess service easier to use. REM first, if any of the argumenst are arrays, convert them to REM vectors of vectors Dim i As Integer For i = LBound( args() ) To UBound( args() ) If IsArray( args( i ) ) Then args( i ) = fnBasicArrayToFuncArray( args( i ) ) End If Next Dim myFuncAccess As Variant, myFuncResult As Variant myFuncAccess = createUNOService( "com.sun.star.sheet.FunctionAccess" ) myFuncResult = myFuncAccess.callFunction( sFunc, args() ) REM now restore any of the array arguments back to normal arrays For i = LBound( args() ) To UBound( args() ) If IsArray( args( i ) ) Then _ args( i ) = fnFuncArrayToBasicArray( args(i) ) Next REM and if the result is an array, convert it from a vector of REM vectors to a normal array If IsArray( myFuncResult ) Then _ myFuncResult = fnFuncArrayToBasicArray( myFuncResult() ) fnCalcFunction() = myFuncResult End Function REM ############################################################## REM ############################################################## REM ############################################################## Function fnBasicArrayToFuncArray( a() ) As Variant REM this function expects to receive a normal OOBasic array such as REM one would create with "Dim a(3)" or "Dim a(2,3)". The function REM then converts this to a "vector of vectors" which is what REM calc uses internally for ranges of cells (data arrays). It is REM these "vectors of vectors" that calc's FunctionAccess service REM is expecting to receive for arrays. REM all the LBound calls in here are to bulletproof against REM non-zero-based arrays. This is especially important because REM calc passes in ones-based arrays Dim nRows As Integer, nCols As Integer nRows = 0 : nCols = 0 On Local Error Resume Next nRows = UBound( a() ) - LBound( a() ) + 1 nCols = UBound( a(), 2 ) - LBound( a(), 2 ) + 1 On Local Error GoTo 0 Dim i As Integer, j As Integer Dim tempVec1( 0 To nRows - 1 ) As Variant Dim tempVec2() As Variant For i = LBound( a() ) To UBound( a() ) REM Since arrays copy by reference instead of value, the following REM DimArray statement is required to create a new tempVec2 each REM time we loop. By the way, DimArray is guaranteed to be zero based. tempVec2 = DimArray( IIf( nCols = 0, 0, nCols - 1 ) ) If nCols > 1 Then For j = LBound( a(), 2 ) To UBound( a(), 2 ) tempVec2( j - LBound( a(), 2 ) ) = a( i, j ) Next ElseIf nCols = 1 Then tempVec2(0) = a( i, 1 ) Else tempVec2(0) = a( i ) End If tempVec1( i - LBound( a() ) ) = tempVec2() Next REM now return the vector of vectors fnBasicArrayToFuncArray() = tempVec1() End Function REM ############################################################## REM ############################################################## REM ############################################################## Function fnFuncArrayToBasicArray( a() ) As Variant REM This function takes a "vector of vectors" and returns a normal REM OOBasic two-dimensional array. The LBound stuff makes it read REM more complicated than it should as I try to bulletproof against REM someone using non-zero-based arrays. Dim nRows As Integer, nCols As Integer nRows = UBound( a() ) - LBound( a() ) + 1 nCols = UBound( a( LBound( a() ) ) ) - LBound( a( LBound( a() ) ) ) + 1 Dim tempArray( 0 To nRows - 1, 0 To nCols - 1 ) As Variant Dim tempCols() As Variant Dim i As Integer, j As Integer For i = 0 To nRows - 1 tempCols() = a( i ) For j = 0 To nCols - 1 tempArray( i, j ) = tempCols( j ) Next Next fnFuncArrayToBasicArray() = tempArray() End Function
np->ab reassign
Complicated example, has to be evaluated, STARTED, OOo Later for now
I have created a somewhat simplier test case - see below. Tested with OO 2.1.0 on Win XP SP2 1.) Put the code into an empty calc doc 2.) Run the code from Basic IDE: no error 3.) Change "Sub Test11" to "Sub Test1" 4.) Run again - it fails in the indicated line Hope that helps a bit, ms777 Sub Test11 Dim lTest as Long lTest = 1 Dim x as Long x = IIF(lTest=1,0,23) x = IIF(lTest=0,0,23) Dim y as String msgbox IIF(lTest=0,"Hello","GoodBye") msgbox IIF(lTest=1,"Hello","GoodBye") 'after modification fails with "Inadmissable value or data type. Data type mismatch" ! x = IIF(lTest=1,0,23) End Sub
Here is ms7777's shorter test case stripped down to extremes, still producing the same error message after running, editing and running it again (sorry, I did not work through pitonyak's long test case). Sub Test11 IIf(true, 0.0, "") IIf(true, 0, "") End Sub Remarks to stripping down: 1) It is not necessary to change the name of the sub, inserting a line break will do just as well. 2) The first argument of the IIf (ie the condition) does not have to be a variable to reproduce the error. 3) IIf works without using its return value. 4) If the first argument evaluates to true, the third argument does not seem to interfere with the error, only the second argument counts. 5) The exact type of the arguments does not matter, the only thing required is that the second arguments differ in type. 6) Run ms7777's code once, edit it, then run it again and again, the error will occure at different rows! I found that in order to reproduce this oscillation effect, at least 4 IIfs are needed: Sub Test11 IIF(true, 0, "") IIF(true, 0.0, "") IIF(true, 0.0, "") IIF(true, 0, "") End Sub I can organize and send further interesting test cases I have found if it helps solving this issue.
Tested all four macros (testCalcFunctions and the 3 versions of Test11) in OOo 3.0 only to reproduce the same behavior. Additional hints to reproduce exactly the same behavior: Be sure to a) use a new Calc document before running each macro for the first time OR b) save it, close it and open it again (OOo does not have to be closed). If you 1) test and delete one macro, then 2) paste and test another, you may get different results.
Another example, in the hope that some developer tackles the problem... Option Explicit Sub Main Dim g as double g = 0 print g, SkipZero(g) print g, SkipZero(g) End Sub function SkipZero(grandeur As Double) As Double SkipZero = IIF(grandeur=0 , 1, grandeur) end function Run the macro Main, the first call to SkipZero is OK but the second call throws error "Inconsistent type of data" at the line with the IIF instruction. If the bug does not appear, change the value of g to 1 and run again. Then change the value back to 0 and run again, it will appear. Once triggered, the error can also appear during evaluation of a cell formula calling SkipZero. I can reproduce the bug on Windows XP with OOo 3.0.0, 2.4.2RC1, 1.1.5 If this instruction cannot be corrected it would be fair to document it as "discontinued, do not use" in the help (F1).
I had the error using: Iif(sVar = "", "", ", ") In my code, sVar is not empty most of the time (condition FALSE). The behavior seamed random on several runs. Note on the comment of hba : I do expect Iif to produce an error if arguments #2 and #3 are not of the same type, since Iif is a function and thus should have a fixed return type, no matter the valuation of #1. It's perfectly fine that Iif(TRUE, 0, "") produces an error. The point of the this issue is that Iif sometimes (more or less unpredictably) produces an error, *even though* the types of #2 and #3 do match. If the function cannot be fixed, it really should be removed. After all it can be easily worked around, and the random errors are a time bomb for the developers.
The problem is still there in OOO 3.2. And it was already there in OOO 2.0.1 (see http://de.narkive.com/2006/1/9/559733-ooo-2-0-1-iif.html) I also think, Iif sould be removed, if it cannot be fixed... Maybe as a first step one can easily add a hint, that this function is depreciated to that error message of Iif (in German: "Unzulässiger Wert oder Datentyp Datentypen unverträglich") This helps already to understand, what's going on. The random occurence of that message is a mess. Best regards kunkunuzzi
Problem is still there. OOO 3.3.0 (9567) Mac. So I've built my own func: '----------------------------------------------------------------- ' Replacement for iif (very unreliable) '----------------------------------------------------------------- Function ifif(bBool as Boolean, sTrue as String, sFalse as String) If bBool then ifif = sTrue Else ifif = sFalse End If End Function
*** Issue 125953 has been marked as a duplicate of this issue. ***
I found the strange difference among the following in disassembled optree. - Original Sub Main IIf(true, 1.0, "") End Sub -- Disassembled Position Op Op1 Op2 OpStr ----------------------------------------- 00000000 Lbl00000000: 00000000 45 00000000 JUMP Lbl0000 // Label ; Sub Main 00000005 Main: 00000005 87 00000003 00000000 STMNT 3,0 (For-Level: 0) ; IIf(true, 1.0, "") 0000000E 87 00000004 00000000 STMNT 4,0 (For-Level: 0) 00000017 18 ARGC // new argv 00000018 80 00000002 0000000C RTL true ; Variant 00000021 19 ARGV 00000022 40 00000004 NUMBER "1" 00000027 19 ARGV 00000028 41 00000005 STRING "" 0000002D 19 ARGV 0000002E 80 00008003 00000000 RTL IIf ; Empty, Args 00000037 1C GET ; End Sub 00000038 87 00000005 00000000 STMNT 5,0 (For-Level: 0) 00000041 2B LEAVE Execute the code above in the Basic IDE and then change like the following. - Second argument is modified Sub Main IIf(true, "", "") End Sub Execute the code to be compiled -> error. -- Disassembled Only position 2E shows important difference from the original. Position Op Op1 Op2 OpStr ----------------------------------------- 0000002E 80 00008003 00000002 RTL IIf ; Integer, Args Op2 is type information. It was changed from Empty to Integer. - After restarting the office The same code above gave correct opcode. 0000002E 80 00008003 00000000 RTL IIf ; Empty, Args The wrong Op2 code gives the error message because impossible to assign the value. Type miss match between desired type and real return value from the function call.
The parser tries to find the information of the function that called by _RTL pcode from the runtime library in SbiParser::CheckRTLForSym function. At first compilation of the code, the runtime library does not have any information about Iif function and its entry is created as SbxMethod instance and stored in SbxObject::pMethods array, see SbiStdObject::Find function. The runtime library is not cleared before the second compilation after the modification of the code. At first execution of the code, the SbxMethod instance created at the compilation is used to call Iif function. The type of return value is set at first execution of the runtime function. Iif is defined as RTLFUNC(Iif) (SbRtl_Iif function is generated by the macro) in basic/source/runtime/methods1.cxx file. If Iif is called like: Iif(true, 1, ""), the return type is going to be SbxINTEGER from the second argument in *rPar.Get(0) = *rPar.Get(2); line of the runtime funtion. In SbxValue::operator= function called at assignment of the return value, last Put method call set the type of return value of the function. SbxMethod::aData (declared in parent SbxValue class) instance variable is used to keep return value from the runtime function and its eType element is used to get its value type. Changes of SbxMethod::aData.eType - first compilation: SbxEMPTY as shown in disassembled code, this match with the function definition in basic/source/runtime/stdobj.cxx file - first execution: depends on the condition and return value of Iif function - second compilation: depends on the result of the last call of Iif function At the second compilation, SbxMethod instance for Iif function having the last value of the function call, is used to obtain the function information. The compilation result is unstable like described in the Comment 11. Return value of most runtime functions are always same type, so this problem revealed on Iif function since its return type can be changed by the coder.
I could reproduce the same problem with Choose runtime function by the procedure written in Comment 11. - Original: Sub Main Choose(1, 1, 2) End Sub 0000002A 80 00008002 00000000 RTL Choose ; Empty, Args - Changed: Sub Main Choose(1, "", 2) End Sub 0000002A 80 00008002 00000002 RTL Choose ; Integer, Args The OpCode2 for the type is wrong for the second code.
Created attachment 84305 [details] Proposed patch to use defined return type for pre-defined runtime functions Add the way to know a SbxMethod instance is dedicated to pre-defined runtime function and use its information at compilation for RTL operation code.
Fixed on trunk, revision 1650325
Created attachment 85090 [details] Document contains macro to confirm this fix To confirm this fix, open the attached document which contains Basic macro. - Choose Tools - Macros - Organize Macros - OpenOffice Basic... - Choose i63614_iif.odt/Standard/Module1 in Macro from tree on the dialog - Click "Edit" button on the dialog to show the Basic IDE - See below or the code for the additonal procedure to confirm. Sub Main ' To confirm, ' 1. execute this code ' 2. change the part of the code according to the following ' 3. click "Step Over" button several times on the toolbar ' -> no "Data type mismatch." dialog should be shown ' msgbox IIf(false, "", 1) ' To confirm, change ' from IIf(true, 1.0, "") ' to IIf(true, "", "") Choose(1, 1, 2) ' To confirm, change ' from Choose(1, 1, 2) ' to Choose(1, "", 2) End Sub