- Joined
- Jul 20, 2018
- Messages
- 177
Before writing it I have googled this topic, but found nothing. Here I describe some bugs in vJass connected with structs. I used JassHelper 0.A.2.B from NewGen pack.
First issue. Lets consider next code.
There is generated code below.
You may notice that
The same issue takes place if I put any multiplier of 8191. For example, for 16382
Second issue is more important.
Here is code.
If you compile it, next message will be thrown.
For some reason JassHelper forgets to generate some variables. OK, let's do it by our own.
After compilation next code will be generated.
JassHelper sees that B is a parent of C and generates function
This would not be so weird unless next case.
Third issue is here.
Function
Forth issue is similar.
Compilation produces next error.
Let's declare
Here function
The most interesting thing is that second and forth issues are handled if struct uses the number of indexes less than 8191.
If the number of indexes is 8191, issues 2-4 take place.
Fifth issue. The most important.
Let's consider code from third issue but for struct with the numer of indexes less than 8191.
Function
I have to mention next thing.
Code above will be converted to code below in all third, forth and fifth issues.
Here it's clearly shown that function
UPD: Actually, for forth issue next code is generated.
This is the only really weird issue. I remind you that this issue is handled for structs with the number of indexes less than 8191.
It is time to conclude. There can be more issues if, for example, struct D extends C and so on, but I do not investigate it. I hope that someone will fix these issues and share with fixed version of JassHelper.
The key points:
Be careful when you extend structs!
First issue. Lets consider next code.
JASS:
struct A[8191]
endstruct
JASS:
globals
//JASSHelper struct globals:
constant integer si__A=1
integer si__A_F=0
integer si__A_I=0
integer array si__A_V
integer array si__A_2V
endglobals
//Generated allocator of A
function s__A__allocate takes nothing returns integer
local integer this=si__A_F
if (this!=0) then
set si__A_F=si__A_V[this]
else
set si__A_I=si__A_I+1
set this=si__A_I
endif
if (this>8191) then
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,1000.,"Unable to allocate id for an object of type: A")
return 0
endif
set si__A_V[this]=-1
return this
endfunction
//Generated destructor of A
function s__A_deallocate takes integer this returns nothing
local integer used
if this==null then
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,1000.,"Attempt to destroy a null struct of type: A")
return
else
set used=si__A_V[this]
if (used!=-1) then
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,1000.,"Double free of type: A")
return
endif
endif
set si__A_V[this]=si__A_F
set si__A_F=this
endfunction
You may notice that
integer array si__A_2V
is redundant.The same issue takes place if I put any multiplier of 8191. For example, for 16382
integer array si__A_3V
is declared which is never used. If I put 16383, this array will be used, but 16383 indexes can be described with two arrays since 16383 = 8192 * 2 - 1 (-1 for null struct). This is true for any integer in range (8191 * k, 8192 * k - 1], where k > 1.Second issue is more important.
Here is code.
JASS:
struct A[16382]
endstruct
struct B extends A
endstruct
struct C extends B
endstruct
For some reason JassHelper forgets to generate some variables. OK, let's do it by our own.
JASS:
globals
integer array si__B_type
integer array si__B_2type
endglobals
JASS:
globals
// Generated
integer array si__B_type
integer array si__B_2type
//JASSHelper struct globals:
constant integer si__A=1
integer si__A_F=0
integer si__A_I=0
integer array si__A_V
integer array si__A_2V
integer array si__A_3V
constant integer si__B=2
constant integer si__C=3
integer array si__A_type
integer array si__A_2type
integer array si__A_3type
trigger array st__A_onDestroy
integer f__arg_this
endglobals
function si__A_getType takes integer this returns integer
if(this<8191) then
return si__A_type[this]
else
return si__A_2type[this-8191]
endif
endfunction
function si__B_getType takes integer this returns integer
if(this<8191) then
return si__B_type[this]
else
return si__B_2type[this-8191]
endif
endfunction
//Generated allocator of A
function s__A__allocate takes nothing returns integer
local integer this=si__A_F
if (this!=0) then
if(this<8191) then
set si__A_F=si__A_V[this]
else
set si__A_F=si__A_2V[this-8191]
endif
else
set si__A_I=si__A_I+1
set this=si__A_I
endif
if (this>16382) then
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,1000.,"Unable to allocate id for an object of type: A")
return 0
endif
if(this<8191) then
set si__A_type[this]=1
else
set si__A_2type[this-8191]=1
endif
if(this<8191) then
set si__A_V[this]=-1
else
set si__A_2V[this-8191]=-1
endif
return this
endfunction
//Generated destructor of A
function sc__A_deallocate takes integer this returns nothing
local integer used
if this==null then
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,1000.,"Attempt to destroy a null struct of type: A")
return
else
if(this<8191) then
set used=si__A_V[this]
else
set used=si__A_2V[this-8191]
endif
if (used!=-1) then
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,1000.,"Double free of type: A")
return
endif
endif
set f__arg_this=this
if(this<8191) then
call TriggerEvaluate(st__A_onDestroy[si__A_type[this]])
else
call TriggerEvaluate(st__A_onDestroy[si__A_2type[this-8191]])
endif
if(this<8191) then
set si__A_V[this]=si__A_F
else
set si__A_2V[this-8191]=si__A_F
endif
set si__A_F=this
endfunction
//Generated allocator of B
function s__B__allocate takes nothing returns integer
local integer this=s__A__allocate()
local integer kthis
if(this==0) then
return 0
endif
if(this<8191) then
set si__A_type[this]=2
else
set si__A_2type[this-8191]=2
endif
set kthis=this
return this
endfunction
//Generated allocator of C
function s__C__allocate takes nothing returns integer
local integer this=s__B__allocate()
local integer kthis
if(this==0) then
return 0
endif
if(this<8191) then
set si__A_type[this]=3
else
set si__A_2type[this-8191]=3
endif
set kthis=this
return this
endfunction
JassHelper sees that B is a parent of C and generates function
si__B_getType
, but this function is useless as C is a descendant of A and uses integer array si__A_V
.This would not be so weird unless next case.
Third issue is here.
JASS:
struct A[16382]
stub method someMethod takes nothing returns integer
return 1
endmethod
endstruct
struct B extends A
stub method someMethod takes nothing returns integer
return 2
endmethod
endstruct
struct C extends B
method someMethod takes nothing returns integer
return 3
endmethod
endstruct
globals
integer array si__B_type
integer array si__B_2type
endglobals
JASS:
globals
// Generated
integer array si__B_type
integer array si__B_2type
//JASSHelper struct globals:
constant integer si__A=1
integer si__A_F=0
integer si__A_I=0
integer array si__A_V
integer array si__A_2V
integer array si__A_3V
constant integer si__B=2
constant integer si__C=3
trigger array st__A_someMethod
trigger array st__B_someMethod
integer array si__A_type
integer array si__A_2type
integer array si__A_3type
trigger array st__A_onDestroy
integer f__arg_this
integer f__result_integer
endglobals
function si__A_getType takes integer this returns integer
if(this<8191) then
return si__A_type[this]
else
return si__A_2type[this-8191]
endif
endfunction
function si__B_getType takes integer this returns integer
if(this<8191) then
return si__B_type[this]
else
return si__B_2type[this-8191]
endif
endfunction
//Generated method caller for A.someMethod
function sc__A_someMethod takes integer this returns integer
set f__arg_this=this
if(this<8191) then
call TriggerEvaluate(st__A_someMethod[si__A_type[this]])
else
call TriggerEvaluate(st__A_someMethod[si__A_2type[this-8191]])
endif
return f__result_integer
endfunction
//Generated allocator of A
function s__A__allocate takes nothing returns integer
local integer this=si__A_F
if (this!=0) then
if(this<8191) then
set si__A_F=si__A_V[this]
else
set si__A_F=si__A_2V[this-8191]
endif
else
set si__A_I=si__A_I+1
set this=si__A_I
endif
if (this>16382) then
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,1000.,"Unable to allocate id for an object of type: A")
return 0
endif
if(this<8191) then
set si__A_type[this]=1
else
set si__A_2type[this-8191]=1
endif
if(this<8191) then
set si__A_V[this]=-1
else
set si__A_2V[this-8191]=-1
endif
return this
endfunction
//Generated destructor of A
function sc__A_deallocate takes integer this returns nothing
local integer used
if this==null then
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,1000.,"Attempt to destroy a null struct of type: A")
return
else
if(this<8191) then
set used=si__A_V[this]
else
set used=si__A_2V[this-8191]
endif
if (used!=-1) then
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,1000.,"Double free of type: A")
return
endif
endif
set f__arg_this=this
if(this<8191) then
call TriggerEvaluate(st__A_onDestroy[si__A_type[this]])
else
call TriggerEvaluate(st__A_onDestroy[si__A_2type[this-8191]])
endif
if(this<8191) then
set si__A_V[this]=si__A_F
else
set si__A_2V[this-8191]=si__A_F
endif
set si__A_F=this
endfunction
//Generated method caller for B.someMethod
function sc__B_someMethod takes integer this returns integer
set f__arg_this=this
if(this<8191) then
call TriggerEvaluate(st__A_someMethod[si__B_type[this]])
else
call TriggerEvaluate(st__A_someMethod[si__B_2type[this-8191]])
endif
return f__result_integer
endfunction
//Generated allocator of B
function s__B__allocate takes nothing returns integer
local integer this=s__A__allocate()
local integer kthis
if(this==0) then
return 0
endif
if(this<8191) then
set si__A_type[this]=2
else
set si__A_2type[this-8191]=2
endif
set kthis=this
return this
endfunction
//Generated method caller for C.someMethod
function sc__C_someMethod takes integer this returns integer
return 3
endfunction
//Generated allocator of C
function s__C__allocate takes nothing returns integer
local integer this=s__B__allocate()
local integer kthis
if(this==0) then
return 0
endif
if(this<8191) then
set si__A_type[this]=3
else
set si__A_2type[this-8191]=3
endif
set kthis=this
return this
endfunction
// ...
function jasshelper__initstructs45407515 takes nothing returns nothing
set st__A_someMethod[1]=CreateTrigger()
call TriggerAddCondition(st__A_someMethod[1],Condition( function sa__A_someMethod))
call TriggerAddAction(st__A_someMethod[1], function sa__A_someMethod)
set st__A_onDestroy[1]=null
set st__A_onDestroy[2]=null
set st__A_onDestroy[3]=null
set st__A_someMethod[2]=CreateTrigger()
call TriggerAddCondition(st__A_someMethod[2],Condition( function sa__B_someMethod))
call TriggerAddAction(st__A_someMethod[2], function sa__B_someMethod)
set st__A_someMethod[3]=CreateTrigger()
call TriggerAddCondition(st__A_someMethod[3],Condition( function sa__C_someMethod))
call TriggerAddAction(st__A_someMethod[3], function sa__C_someMethod)
endfunction
Function
sc__B_someMethod
uses integer array si__B_type
and integer array si__B_2type
which are never filled, therefore, no method will be called as no struct has type 0.Forth issue is similar.
JASS:
struct A[16382]
endstruct
struct B extends A
stub method someMethod takes nothing returns integer
return 2
endmethod
endstruct
struct C extends B
method someMethod takes nothing returns integer
return 3
endmethod
endstruct
globals
integer array si__B_type
integer array si__B_2type
endglobals
Let's declare
trigger array st__A_someMethod
and compile.
JASS:
globals
// Generated
integer array si__B_type
integer array si__B_2type
trigger array st__A_someMethod
//JASSHelper struct globals:
constant integer si__A=1
integer si__A_F=0
integer si__A_I=0
integer array si__A_V
integer array si__A_2V
integer array si__A_3V
constant integer si__B=2
constant integer si__C=3
trigger array st__B_someMethod
integer array si__A_type
integer array si__A_2type
integer array si__A_3type
trigger array st__A_onDestroy
integer f__arg_this
integer f__result_integer
endglobals
function si__A_getType takes integer this returns integer
if(this<8191) then
return si__A_type[this]
else
return si__A_2type[this-8191]
endif
endfunction
function si__B_getType takes integer this returns integer
if(this<8191) then
return si__B_type[this]
else
return si__B_2type[this-8191]
endif
endfunction
//Generated allocator of A
function s__A__allocate takes nothing returns integer
local integer this=si__A_F
if (this!=0) then
if(this<8191) then
set si__A_F=si__A_V[this]
else
set si__A_F=si__A_2V[this-8191]
endif
else
set si__A_I=si__A_I+1
set this=si__A_I
endif
if (this>16382) then
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,1000.,"Unable to allocate id for an object of type: A")
return 0
endif
if(this<8191) then
set si__A_type[this]=1
else
set si__A_2type[this-8191]=1
endif
if(this<8191) then
set si__A_V[this]=-1
else
set si__A_2V[this-8191]=-1
endif
return this
endfunction
//Generated destructor of A
function sc__A_deallocate takes integer this returns nothing
local integer used
if this==null then
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,1000.,"Attempt to destroy a null struct of type: A")
return
else
if(this<8191) then
set used=si__A_V[this]
else
set used=si__A_2V[this-8191]
endif
if (used!=-1) then
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,1000.,"Double free of type: A")
return
endif
endif
set f__arg_this=this
if(this<8191) then
call TriggerEvaluate(st__A_onDestroy[si__A_type[this]])
else
call TriggerEvaluate(st__A_onDestroy[si__A_2type[this-8191]])
endif
if(this<8191) then
set si__A_V[this]=si__A_F
else
set si__A_2V[this-8191]=si__A_F
endif
set si__A_F=this
endfunction
//Generated method caller for B.someMethod
function sc__B_someMethod takes integer this returns integer
set f__arg_this=this
if(this<8191) then
call TriggerEvaluate(st__A_someMethod[si__B_type[this]])
else
call TriggerEvaluate(st__A_someMethod[si__B_2type[this-8191]])
endif
return f__result_integer
endfunction
//Generated allocator of B
function s__B__allocate takes nothing returns integer
local integer this=s__A__allocate()
local integer kthis
if(this==0) then
return 0
endif
if(this<8191) then
set si__A_type[this]=2
else
set si__A_2type[this-8191]=2
endif
set kthis=this
return this
endfunction
//Generated method caller for C.someMethod
function sc__C_someMethod takes integer this returns integer
return 3
endfunction
//Generated allocator of C
function s__C__allocate takes nothing returns integer
local integer this=s__B__allocate()
local integer kthis
if(this==0) then
return 0
endif
if(this<8191) then
set si__A_type[this]=3
else
set si__A_2type[this-8191]=3
endif
set kthis=this
return this
endfunction
// ...
function jasshelper__initstructs45097343 takes nothing returns nothing
set st__A_onDestroy[1]=null
set st__A_onDestroy[2]=null
set st__A_onDestroy[3]=null
set st__B_someMethod[2]=CreateTrigger()
call TriggerAddCondition(st__B_someMethod[2],Condition( function sa__B_someMethod))
call TriggerAddAction(st__B_someMethod[2], function sa__B_someMethod)
set st__B_someMethod[3]=CreateTrigger()
call TriggerAddCondition(st__B_someMethod[3],Condition( function sa__C_someMethod))
call TriggerAddAction(st__B_someMethod[3], function sa__C_someMethod)
endfunction
Here function
sc__B_someMethod
is totally broken. It uses empty integer array si__B_type
, integer array si__B_2type
and trigger array st__A_someMethod
.The most interesting thing is that second and forth issues are handled if struct uses the number of indexes less than 8191.
If the number of indexes is 8191, issues 2-4 take place.
Fifth issue. The most important.
Let's consider code from third issue but for struct with the numer of indexes less than 8191.
JASS:
struct A
stub method someMethod takes nothing returns integer
return 1
endmethod
endstruct
struct B extends A
stub method someMethod takes nothing returns integer
return 2
endmethod
endstruct
struct C extends B
method someMethod takes nothing returns integer
return 3
endmethod
endstruct
JASS:
globals
// Generated
trigger gg_trg_GeneratedCode= null
//integer array si__B_type
//integer array si__B_2type
//trigger array st__A_someMethod
//JASSHelper struct globals:
constant integer si__A=1
integer si__A_F=0
integer si__A_I=0
integer array si__A_V
constant integer si__B=2
constant integer si__C=3
trigger array st__A_someMethod
trigger array st__B_someMethod
integer array si__A_type
trigger array st__A_onDestroy
integer f__arg_this
integer f__result_integer
endglobals
//Generated method caller for A.someMethod
function sc__A_someMethod takes integer this returns integer
set f__arg_this=this
call TriggerEvaluate(st__A_someMethod[si__A_type[this]])
return f__result_integer
endfunction
//Generated allocator of A
function s__A__allocate takes nothing returns integer
local integer this=si__A_F
if (this!=0) then
set si__A_F=si__A_V[this]
else
set si__A_I=si__A_I+1
set this=si__A_I
endif
if (this>8190) then
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,1000.,"Unable to allocate id for an object of type: A")
return 0
endif
set si__A_type[this]=1
set si__A_V[this]=-1
return this
endfunction
//Generated destructor of A
function sc__A_deallocate takes integer this returns nothing
if this==null then
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,1000.,"Attempt to destroy a null struct of type: A")
return
elseif (si__A_V[this]!=-1) then
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,1000.,"Double free of type: A")
return
endif
set f__arg_this=this
call TriggerEvaluate(st__A_onDestroy[si__A_type[this]])
set si__A_V[this]=si__A_F
set si__A_F=this
endfunction
//Generated method caller for B.someMethod
function sc__B_someMethod takes integer this returns integer
set f__arg_this=this
call TriggerEvaluate(st__B_someMethod[si__A_type[this]])
return f__result_integer
endfunction
//Generated allocator of B
function s__B__allocate takes nothing returns integer
local integer this=s__A__allocate()
local integer kthis
if(this==0) then
return 0
endif
set si__A_type[this]=2
set kthis=this
return this
endfunction
//Generated method caller for C.someMethod
function sc__C_someMethod takes integer this returns integer
return 3
endfunction
//Generated allocator of C
function s__C__allocate takes nothing returns integer
local integer this=s__B__allocate()
local integer kthis
if(this==0) then
return 0
endif
set si__A_type[this]=3
set kthis=this
return this
endfunction
// ...
function jasshelper__initstructs46862484 takes nothing returns nothing
set st__A_someMethod[1]=CreateTrigger()
call TriggerAddCondition(st__A_someMethod[1],Condition( function sa__A_someMethod))
call TriggerAddAction(st__A_someMethod[1], function sa__A_someMethod)
set st__A_onDestroy[1]=null
set st__A_onDestroy[2]=null
set st__A_onDestroy[3]=null
set st__A_someMethod[2]=CreateTrigger()
call TriggerAddCondition(st__A_someMethod[2],Condition( function sa__B_someMethod))
call TriggerAddAction(st__A_someMethod[2], function sa__B_someMethod)
set st__A_someMethod[3]=CreateTrigger()
call TriggerAddCondition(st__A_someMethod[3],Condition( function sa__C_someMethod))
call TriggerAddAction(st__A_someMethod[3], function sa__C_someMethod)
endfunction
Function
sc__B_someMethod
uses empty trigger array st__B_someMethod
, thus, no actions will be done.I have to mention next thing.
JASS:
function f takes nothing returns nothing
local B b = C.create()
call DisplayTextToPlayer(Player(0), 0., 0., I2S(b.someMethod()))
endfunction
JASS:
function f takes nothing returns nothing
local integer b= s__C__allocate()
call DisplayTextToPlayer(Player(0), 0., 0., I2S(sc__A_someMethod(b)))
endfunction
sc__B_someMethod
is totally useless, and all bugs connected with it can be ignored.UPD: Actually, for forth issue next code is generated.
JASS:
function f takes nothing returns nothing
local integer b= s__C__allocate()
call DisplayTextToPlayer(Player(0), 0., 0., I2S(sc__B_someMethod(b)))
endfunction
It is time to conclude. There can be more issues if, for example, struct D extends C and so on, but I do not investigate it. I hope that someone will fix these issues and share with fixed version of JassHelper.
The key points:
- For structs with the number of indexes which equals to multipliers of 8191, JassHelper generates arrays which are never used.
- For structs with the number of indexes which equals to any integer in range (8191 * k, 8192 * k - 1], where k > 1, JassHelper generates more arrays than it's really needed.
- For structs with the number of indexes more than 8190 and with descendants which have their own descendants, JassHelper generates functions of getting type for intermediate parents. These functions are never used.
- Consider next struct: it has stub methods, descendants in which these methods are also stub, and its descendants have their own descendants. JassHelper generates callers for mentioned stub methods which are never used.
- Consider next struct: it uses the number of indexes greater than 8190, has descendants which have stub methods and their own descendants. JassHelper generates broken callers for mentioned stub methods.
Be careful when you extend structs!
Attachments
Last edited: