What is the reason for this permissive compiler behaviour?

Just starting out? Need help? Post your questions and find answers here.
User avatar
kpeters58
Enthusiast
Enthusiast
Posts: 341
Joined: Tue Nov 22, 2011 5:11 pm
Location: Kelowna, BC, Canada

What is the reason for this permissive compiler behaviour?

Post by kpeters58 »

Every other programming language I have ever come across is able to detect the range violation in the for loop below. Some newer ones are so smart that they even detect things like these at compile time.

Why is PB so dangerously permissive?

What we see below, is a common one-off error and the compiler just shrugs it off instead of letting the programmer know that there aren't as many elements as (s)he seems to think.

Code: Select all

If OpenWindow(0, 0, 0, 300, 200, "ListViewGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
   ListViewGadget(1, 10, 10, 250, 120)
   For i = 1 To 12
     AddGadgetItem (1, -1, "Item " + Str(i))
   Next
   For i = 0 To CountGadgetItems(1)
     Debug GetGadgetItemText(1, i)
   Next  
  Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf
PB 5.73 on Windows 10 & OS X High Sierra
BarryG
Addict
Addict
Posts: 3331
Joined: Thu Apr 18, 2019 8:17 am

Re: What is the reason for this permissive compiler behaviou

Post by BarryG »

kpeters58 wrote:the compiler just shrugs it off instead of letting the programmer know that there aren't as many elements as (s)he seems to think.
Here's another example without CountGadgetItems():

Code: Select all

If OpenWindow(0, 0, 0, 300, 200, "ListViewGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
   ListViewGadget(1, 10, 10, 250, 120)
   For i = 1 To 5
     AddGadgetItem (1, -1, "Item " + Str(i))
   Next
   For i = 6 To 10
     Debug GetGadgetItemText(1, i)
   Next 
  Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf
User avatar
NicTheQuick
Addict
Addict
Posts: 1227
Joined: Sun Jun 22, 2003 7:43 pm
Location: Germany, Saarbrücken
Contact:

Re: What is the reason for this permissive compiler behaviou

Post by NicTheQuick »

The compiler can not detect runtime errors. GetGadgetItemText() is a runtime function. You could have manipulated the gadget's item count from somewhere else.
The english grammar is freeware, you can use it freely - But it's not Open Source, i.e. you can not change it or publish it in altered way.
Bitblazer
Enthusiast
Enthusiast
Posts: 736
Joined: Mon Apr 10, 2017 6:17 pm
Location: Germany
Contact:

Re: What is the reason for this permissive compiler behaviou

Post by Bitblazer »

NicTheQuick wrote:The compiler can not detect runtime errors. GetGadgetItemText() is a runtime function. You could have manipulated the gadget's item count from somewhere else.
+1 - not only by another thread inside the own executable, but also by manipulation from another software running on the system.

PureBasic gives big freedom, but that also includes this freedom to screw up any reference or index. If you have a real problem with this, i would suggest writing your own procedure or macro to replace any gadget access and simply do plenty of range checks in those procedures. If you implement access with range checks as a Macro, you can use a complex macro with safety checks, logging and catching errors during development and once the software is stable enough, you simply replace the macro with a version which just does the access without range checks.
That is actually a pretty useful way to trace processing of data not only to catch errors and bugs, but also to find performance bottlenecks, logical dataflow mistakes and optimize the data/instruction flow during development and still having a fast and slim release binary.

Macros are powerfull for that. Obviously that also means if you screw up your access macros, your software will behave very myteriously ;)

Example Macro i use :

Code: Select all

Macro AddMessage(Message, Level)
  SendMessageToPort(Message, 555, Level)
  CompilerIf #PB_Compiler_Debugger  ; Only enable extended logging in debug mode during development
    SendMessageToPort(Str(Level) + "," + #PB_Compiler_Procedure + "(" + #PB_Compiler_Line + ")" + Message, 555)
  CompilerEndIf
EndMacro
During development, i usually run from the PB IDE where i have the debugger enabled. So i use that knowledge and the #PB_Compiler_Debugger Flag to have additional code for logging to my custom debugging software.

In your case you would write a macro to replace all gadget functions for your listicongadget and have optional range checks in the macros during development. It is crucial to replace all reference calls to the gadgets with your new macro functions. But once you did that, you have a lot new very useful options to find bugs and optimize your software.
Last edited by Bitblazer on Fri Jun 05, 2020 12:11 pm, edited 5 times in total.
infratec
Always Here
Always Here
Posts: 6883
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: What is the reason for this permissive compiler behaviou

Post by infratec »

kpeters58 wrote:Every other programming language I have ever come across is able to detect the range violation in the for loop below
Have you ever tried C :mrgreen:

Btw. you can use the Purifier with a Granularity of 1,1,1,1

Enable Purifier in compiler compiling/start and use

Code: Select all

PurifierGranularity(1, 1, 1, 1)
Ups ... in this case it doesn't help :oops:

Maybe GetGadgetItemText() does an internal check and returns an empty string if an error is detected.
Last edited by infratec on Fri Jun 05, 2020 11:49 am, edited 1 time in total.
BarryG
Addict
Addict
Posts: 3331
Joined: Thu Apr 18, 2019 8:17 am

Re: What is the reason for this permissive compiler behaviou

Post by BarryG »

Shouldn't a runtime error be raised if you try to read an item that doesn't exist? Why just return an empty string? As seen in my example above (items 6 to 10).

Because how can you know if you've read a non-existent item by mistake, or an existing item that was in fact just empty?
User avatar
Derren
Enthusiast
Enthusiast
Posts: 313
Joined: Sat Jul 23, 2011 1:13 am
Location: Germany

Re: What is the reason for this permissive compiler behaviou

Post by Derren »

Why does it matter?
You're introducing paradigms from other languages into basic here
Image

It's good to know this in some situations, but in most cases you only need to check if the first picture is true (i.e. a value is returned) or not. Whether or not something exists should be checked with functions like Is...() (IsGadget() etc).

Code like this is just bad practice.

Code: Select all

For i=0 To 1000
  If GetGadgetItemText() <> "" : counter +1 : EndIf
Next
Debug "Of 1000 entries "+Str(counter)+" were not empty"
There are not a thousand entries and you should check how many are there, beforehand.
I can imagine the system trying to check non-existent stuff all the time, is a "huge" waste of CPU-time.

The discrepancy between how many items are there, and what is the last index, is annoying, but it's common and makes sense (if you realize we start at 0 in most languages --> There are 2 items, they have the index 0 and 1. There is NO item with an index of 2)
JavaScript, for example.

Code: Select all

var string = "Hello";
var len = string.length
for (i=0; i<len; i++){
  console.log( string[i] );
}
The key here is the i<len.
PB doesn't have this kind of For-loops, so you need use "-1" in order to always stay under the limit, or use a while loop.

Code: Select all

Define i=0;
While i<numberOfItems
  Debug GetGadgetItemText(Gadget, i)
  i+1
Wend
Little John
Addict
Addict
Posts: 4527
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: What is the reason for this permissive compiler behaviou

Post by Little John »

BarryG wrote:Shouldn't a runtime error be raised if you try to read an item that doesn't exist? Why just return an empty string? As seen in my example above (items 6 to 10).

Because how can you know if you've read a non-existent item by mistake, or an existing item that was in fact just empty?
Yep! I absolutely agree with this message.
User avatar
Tenaja
Addict
Addict
Posts: 1949
Joined: Tue Nov 09, 2010 10:15 pm

Re: What is the reason for this permissive compiler behaviou

Post by Tenaja »

It returns an empty string because the return type is string... Which is a pointer.
Little John
Addict
Addict
Posts: 4527
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: What is the reason for this permissive compiler behaviou

Post by Little John »

Tenaja wrote:It returns an empty string because the return type is string... Which is a pointer.
The problem is, as BarryG wrote, that returning an empty string does not unambiguously indicate an error. Because an empty string can also be the content of a valid entry, which is just ... empty.
BarryG
Addict
Addict
Posts: 3331
Joined: Thu Apr 18, 2019 8:17 am

Re: What is the reason for this permissive compiler behaviou

Post by BarryG »

Tenaja wrote:It returns an empty string because the return type is string
But it also returns an empty string if you try to read a non-existing item. This is bad.

Run this code, and then enter 3 when prompted, and then 6, and lastly enter 100 (as though you made a typo for "10"). It says 100 is the same as 3 and 6, which is plainly wrong.

Code: Select all

If OpenWindow(0, 300, 300, 300, 200, "ListViewGadget", #PB_Window_SystemMenu)
  ListViewGadget(1, 10, 10, 250, 180)
  For i = 1 To 11
    AddGadgetItem (1, -1, "Index " + Str(i-1))
  Next
  SetGadgetItemText(1, 3, "")
  SetGadgetItemText(1, 6, "")
  Repeat
    i$=InputRequester("Question","Enter an index number to read:","")
    If i$<>""
      item$=GetGadgetItemText(1,Val(i$))
      MessageRequester("Answer","Item read was "+Chr(34)+item$+Chr(34))
    EndIf
  Until i$=""
  Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf
User avatar
Derren
Enthusiast
Enthusiast
Posts: 313
Joined: Sat Jul 23, 2011 1:13 am
Location: Germany

Re: What is the reason for this permissive compiler behaviou

Post by Derren »

Code: Select all

OpenWindow(0, 0, 0, 100, 200, "test")
StringGadget(0, 0, 0, 100, 100, "") ;does _not_ support GadgetItems
Debug SetGadgetItemText(0, 10, "how about this one?")
Debug GetGadgetItemText(0, 10)
:lol:

Seriously, these indexes are not IDs, which means their value will always lie in [0; CountGadgetItems()[
It's one line to check if that is true.

@BarryG: If the item-number is in that range and the return string is empty, it means the item-text is empty.
If the range is outside, why even check the ItemText in the first place?
Bitblazer
Enthusiast
Enthusiast
Posts: 736
Joined: Mon Apr 10, 2017 6:17 pm
Location: Germany
Contact:

Re: What is the reason for this permissive compiler behaviou

Post by Bitblazer »

BarryG wrote:Shouldn't a runtime error be raised if you try to read an item that doesn't exist? Why just return an empty string? As seen in my example above (items 6 to 10).

Because how can you know if you've read a non-existent item by mistake, or an existing item that was in fact just empty?
Before accessing an entry, you should get the number of existing entries with CountGadgetItems(). In this case the bug would be to ignore this info and accessing an item outside the number of entries. This is a programming error that can be easily avoided in a singlethread situation if you are sure nobody else accesses (or manipulates) the gadget. Let's not talk about multithreading concurrent access or even an external process manipulating gadgets.

The norm is - one thread accesses a gadget (at once) and you use functions like CountGadgetItems() to not adress items outside the range. If the developers screws up the index reference or doesnt even use CountGadgetItems() to avoid this, thats not the fault of the programming language ;)

But i agree that better exception and error handling would be good. But Purebasic gives us all the tools we need to catch problems like out-of-range index access (macros are really helpfull here if you use them wisely).
BarryG
Addict
Addict
Posts: 3331
Joined: Thu Apr 18, 2019 8:17 am

Re: What is the reason for this permissive compiler behaviou

Post by BarryG »

I know about CountGadgetItems(), and I use it myself. Just wishing PureBasic wouldn't return an empty string for a bad index. It's the same for InputRequester() too; this returns an empty string when the user cancels it, so how do we know if the user actually wanted to specify an empty string, or just to cancel the requester? That's why I have to use my own input requester instead, so I can tell the difference.
Bitblazer
Enthusiast
Enthusiast
Posts: 736
Joined: Mon Apr 10, 2017 6:17 pm
Location: Germany
Contact:

Re: What is the reason for this permissive compiler behaviou

Post by Bitblazer »

BarryG wrote:(snip)
Basic rule in IT - NEVER trust any data the user inputs without doublechecking it. What you did there was using a user input as an index to reference a gadget entry outside the valid range. You did not do the necessary check if the user entered an index which is actually outside the valid range! We all (should) know how negative numbers are encoded internally, imagine a user entering an unchecked -1 unstead of the 100 ...

If you do that in other more restrictive languages, your user will see a stack- / registerdump due to the illegal reference. Users should never see those things because they confuse the hell out of them and your support will spend time dealing with a confused user. So even in C# you would precheck any user input index before using it and that precheck would pop up a reminder that the index is outside a valid range. In C# you would do it to avoid an application exception, in Purebasic you need to do it to avoid faulty data processing.

Both cases need to be caught by your software! But again, i agree with you that the purebasic error handling should (optionally) be able to catch these illegal references and not act "randomly" like it does currently.

Basic rule of data processing in information technology - never trust any data the user enters - double and tripplecheck it. Any error that happens by not doing that, will result in problems and the user will blame your software before even considering that they made a input mistake ;)

Plus - dont forget that there are also users intentionally entering malicious data to have your software crash or exploit it to gain unauthorized access.
Last edited by Bitblazer on Fri Jun 05, 2020 1:30 pm, edited 7 times in total.
Post Reply