• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

SimpleLeaksFinder

Status
Not open for further replies.
Level 13
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.

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
 

Attachments

  • bytecode-boilerplate.j
    10.3 KB · Views: 57
Status
Not open for further replies.
Top