- Joined
- Nov 7, 2014
- Messages
- 571
A very simple leaks finder that goes over the map script's bytecode instructions to find any local variables that have not been nulled.
I suppose it won't be very useful because of the false positives (enums, player handles, etc.) but I just liked the idea of doing "introspection" on the bytecode level (skips the parsing of the script part).
There are of course much better tools, like: Jass Script Helper.
I suppose it won't be very useful because of the false positives (enums, player handles, etc.) but I just liked the idea of doing "introspection" on the bytecode level (skips the parsing of the script part).
There are of course much better tools, like: Jass Script Helper.
JASS:
// A very simple leaks finder that goes over the map scripts bytecode instructions
// to find any local variables that haven not been nulled.
//
// Enums (playerunitevent, gametype) and player handles when stored to
// locals are reported as leaks when in fact enums are just integers and player
// handles are cached.
//
// Handles not stored to locals are not reported:
// call CreateTimer()
// call CreateTimer()
// call CreateTimer()
//
library SimpleLeaksFinder initializer init uses BytecodeBoilerplate
private function dd takes string s returns nothing
call BJDebugMsg(s)
endfunction
private function expect takes boolean b, string s returns nothing
if not b then
call dd("|cffFF0000expected: " + s + "|r")
if 1 / 0 == 1 then
endif
endif
endfunction
//# +nosemanticerror
private function get_first_instr_of_WidgetDropItem takes nothing returns integer
return C2I(function WidgetDropItem)
endfunction
private function find_first_war3mapj_func takes nothing returns integer
local integer offset
local integer opcode
// local string func_name
set offset = get_first_instr_of_WidgetDropItem()
// offset now points to the last blizzard.j function's first instruction
loop
exitwhen Memory[offset/4] == 0x04000000
set offset = offset + 8
endloop
set offset = offset + 8
call expect(Memory[offset/4] == 0x03000000, "war3map.j globals init function")
// skip the war3map.j's globals initialization function
loop
exitwhen Memory[offset/4] == 0x04000000
set offset = offset + 8
endloop
set offset = offset + 8
set opcode = Memory[offset/4] / 0x01000000
call expect(opcode == 0x03, "a function declaration (find_first_war3mapj_func)")
// set func_name = ReadStringFromId(Memory[offset/4 + 4/4])
// call dd("first war3map.j function is: " + func_name)
return offset
endfunction
globals
private hashtable ht = InitHashtable()
endglobals
private function ignore_list_add_function takes string func_name returns nothing
call SaveInteger(ht, 0, StringHash(func_name), 1)
endfunction
private function is_function_ignored takes string func_name returns boolean
return 1 == LoadInteger(ht, 0, StringHash(func_name))
endfunction
private function ignored_functions_list_init takes nothing returns nothing
call ignore_list_add_function("CreateUnitsForPlayer0") // preplaced units for Player(0), autogenerated by WE
endfunction
globals
private integer array locals
private integer locals_count = 0
endglobals
private function set_local takes integer var_stid returns nothing
call SaveInteger(ht, 1, var_stid, 1)
endfunction
private function null_local takes integer var_stid returns nothing
call SaveInteger(ht, 1, var_stid, 0)
endfunction
private function is_local_nulled takes integer var_stid returns boolean
return 0 == LoadInteger(ht, 1, var_stid)
endfunction
// for each function we keep a list of local variables of type 0x07 (handle)
//
private function add_local takes integer var_stid returns nothing
call SaveInteger(ht, 2, var_stid, 1)
set locals[locals_count] = var_stid
set locals_count = locals_count + 1
call null_local(var_stid)
endfunction
private function is_leaky_local takes integer var_stid returns boolean
return 1 == LoadInteger(ht, 2, var_stid)
endfunction
private function clear_locals takes nothing returns nothing
call FlushChildHashtable(ht, 1)
call FlushChildHashtable(ht, 2)
set locals_count = 0
endfunction
globals
private integer ef_offset
private integer ef_leaks_count
private string ef_leaky_locals
endglobals
private function find_locals_leaks takes nothing returns nothing
local integer offset = ef_offset
local integer instr
local integer opcode
local integer var_stid
local integer var_type
local integer leaks_count
local string leaky_locals
local integer src_reg
local integer i
call clear_locals()
loop
set instr = Memory[offset/4]
set opcode = instr / 0x01000000
exitwhen opcode == 0x04
if opcode == 0x05 then // local declaration
set var_type = (instr * 0x100) / 0x01000000
if var_type == 0x07 then // of type handle
set var_stid = Memory[offset/4 + 4/4]
call add_local(var_stid)
endif
elseif opcode == 0x11 then // set variable
set var_stid = Memory[offset/4 + 4/4]
if is_leaky_local(var_stid) then
set src_reg = (instr * 0x00000100) / 0x01000000
if src_reg == 0x00 then
set instr = Memory[(offset - 8)/4]
set opcode = instr / 0x01000000
if opcode == 0x15 or opcode == 0x16 then
call set_local(var_stid)
endif
else
set instr = Memory[(offset - 8)/4]
set var_type = (instr * 0x00010000) / 0x01000000
if var_type == 0x02 then
call null_local(var_stid)
else // if var_type == 0x07 then
call set_local(var_stid)
endif
endif
endif
endif
set offset = offset + 8
endloop
set leaks_count = 0
set leaky_locals = ""
set i = 0
loop
exitwhen i >= locals_count
set var_stid = locals[i]
if not is_local_nulled(var_stid) then
set leaks_count = leaks_count + 1
set leaky_locals = leaky_locals + " " + ReadStringFromId(var_stid) + "\n"
endif
set i = i + 1
endloop
set ef_offset = offset
set ef_leaks_count = leaks_count
set ef_leaky_locals = leaky_locals
endfunction
private function main takes nothing returns nothing
local integer offset = find_first_war3mapj_func()
local integer opcode
local string func_name
call ignored_functions_list_init()
loop
set opcode = Memory[offset/4] / 0x01000000
exitwhen opcode == 0x01 // end of script
call expect(opcode == 0x03, "a function declaration (main)")
set func_name = ReadStringFromId(Memory[offset/4 + 4/4])
set ef_offset = offset + 8 // skip function declaration
call ForForce(bj_FORCE_PLAYER[0], function find_locals_leaks)
set offset = ef_offset + 8 // skip endfunction
if ef_leaks_count > 0 and not is_function_ignored(func_name) then
call dd(func_name)
call dd(ef_leaky_locals)
endif
endloop
call ClearTextMessages()
call DisplayTextToPlayer(GetLocalPlayer(), 0.0, 0.0, "SimpleLeaksFinder: open the Log (F12)")
endfunction
private function init takes nothing returns nothing
call TimerStart(CreateTimer(), 0.0, false, function main)
endfunction
endlibrary