Issue 63614 - IIF is fragile in StarBasic
Summary: IIF is fragile in StarBasic
Status: RESOLVED FIXED
Alias: None
Product: General
Classification: Code
Component: scripting (show other issues)
Version: OOo 2.0.2
Hardware: All All
: P3 Trivial with 3 votes (vote)
Target Milestone: 4.2.0
Assignee: ab
QA Contact:
URL: http://www.oooforum.org/forum/viewtop...
Keywords:
: 125953 (view as issue list)
Depends on:
Blocks:
 
Reported: 2006-03-25 13:14 UTC by andrew
Modified: 2015-10-31 16:16 UTC (History)
7 users (show)

See Also:
Issue Type: DEFECT
Latest Confirmation in: ---
Developer Difficulty: ---


Attachments
Proposed patch to use defined return type for pre-defined runtime functions (3.93 KB, patch)
2014-12-22 17:37 UTC, hanya
no flags Details | Diff
Document contains macro to confirm this fix (9.52 KB, application/vnd.oasis.opendocument.text)
2015-10-31 16:16 UTC, hanya
no flags Details

Note You need to log in before you can comment on or make changes to this issue.
Description andrew 2006-03-25 13:14:52 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
Comment 1 noel.power 2006-03-29 15:28:13 UTC
np->ab reassign
Comment 2 ab 2006-05-17 16:21:08 UTC
Complicated example, has to be evaluated, STARTED, OOo Later for now
Comment 3 ms7777 2006-12-29 14:39:35 UTC
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
Comment 4 hba 2008-11-03 07:15:36 UTC
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.
Comment 5 hba 2008-11-03 10:38:56 UTC
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.
Comment 6 bmarcelly 2008-11-07 17:16:01 UTC
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).
Comment 7 rom1_t 2009-10-04 18:56:49 UTC
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.
Comment 8 kunkunuzzi 2010-03-18 19:35:57 UTC
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
Comment 9 r_rethmann 2011-03-24 14:25:31 UTC
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
Comment 10 oooforum (fr) 2014-12-19 10:48:49 UTC
*** Issue 125953 has been marked as a duplicate of this issue. ***
Comment 11 hanya 2014-12-21 10:33:43 UTC
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.
Comment 12 hanya 2014-12-21 16:00:18 UTC
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.
Comment 13 hanya 2014-12-22 08:39:53 UTC
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.
Comment 14 hanya 2014-12-22 17:37:59 UTC
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.
Comment 15 hanya 2015-01-09 06:19:29 UTC
Fixed on trunk, revision 1650325
Comment 16 hanya 2015-10-31 16:16:48 UTC
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