пятница, 28 марта 2014 г.

Анализируем исходный код 30 apk с помощью PMD

Как — то раз, бродя по просторам интернета, я наткнулся на PMD. PMD служит для поиска «общих недостатков» кода, таких как пустые блоки, создание объекта, который никогда не используется, и так далее. Кроме того, в составе PMD присутствует CPD(copy-paste-detector), который может находить дублирование кода. PMD полностью поддерживает такие языки как Java, JavaScript, XML, XSL, а CPD может работать и с более длинным списком технологий: Java, C, C + +, C #, PHP, Ruby, Fortran, JavaScript.

Стало интересно, а сколько же таких «общих недостатков» можно найти скажем в 30 приложениях Android, которые спокойно лежат себе на 4pda? 


1. Необходимо преобразовать .apk в .jar.

Для этого нужно использовать всем известную dex2jar. Использовать ее очень просто: распаковываем, да пишем примерно следующее: 

./dex2jar.sh имя_вашего_apk.apk.

После процесса преобразования в вашей папке вы найдете файл — «имя_вашего_apk_dex2jar.jar». В этом архиве уже лежат классы, необходимые для последующего анализа, но в не читабельном виде, так сказать. Для того, чтобы мы могли как следует покопаться в нормальных классах, нужно их декомпилировать. 


2. Преобразовываем код из бинарников в читаемый вид.


Из всех проверенных мной преобразователей самым удачным выглядит fernflower. Чтобы воспользоваться им, нужно выполнить команду: 

java -jar fernflower.jar имя_вашего_apk_dex2jar.jar имя_директории

Создастся папка с названием «имя_директории», в которой будет лежать файл с таким же именем, как и исходный. Но в этом случае код можно анализировать.


3. Распаковка архива.


Jar и apk файлы являются архивами, так что их можно запросто распаковать с помощью утилиты unzip.

unzip имя_файла -d имя_директории

4. Натравливаем PMD.


Для анализа исходного кода PMD использует определенный набор правил. Весь список можно глянуть вот тут: правила. Но мы будем использовать всего два набора: android и basic. Для анализа нужно прописать следующую строчку:

./run.sh pmd -d + имя_директории_с_исходниками -f text -R rulesets/java/basic.xml,rulesets/java/android.xml -version 1.7 -language java

В общем — то все, осталось лишь объединить все шаги в единый скрипт и подвести статистику.


5. Пишем простой скрипт на python. 


Сначала обговорим структуру проекта. В корне пусть будут папки: apks(куда мы положим 30 наших произвольных apk'ашек), dex2jar(где будет лежать распакованная одноименная программа), pmd(куда распакуем архив с pmd), наш сприпт. 



import commands
import os
import fnmatch
import matplotlib.pyplot as plt
import collections


abs_path_dir = os.getcwd()
read_jars_folder_name = abs_path_dir + "/apks/read_jars"
text_file_errors = open('all_errors', 'w')


def ensure_dir(f):
    d = os.path.dirname(f)
    if not os.path.exists(d):
        os.makedirs(d)


def covert_to_jar(name_apk):
    os.system(abs_path_dir + "/dex2jar/dex2jar.sh " +
              os.getcwd() + "/apks/" + name_apk)


def conver_to_read_jar(name_file, name_folder):
    full_dir_name = abs_path_dir + "/apks/" + name_file
    path = "java -jar " + abs_path_dir + "/projects/fernflower.jar " + \
        full_dir_name + " " + name_folder
    os.system(path)


def get_all_files_with_pattern(pattern, folder):
    files = []
    for f in os.listdir(folder):
        if fnmatch.fnmatch(f, pattern):
            files.append(f)
    return files


def convert_all_apk_to_jar():
    for f in get_all_files_with_pattern("*.apk", abs_path_dir + "/apks/"):
        covert_to_jar(f)


def convert_all_jar_to_read_jar():
    ensure_dir(read_jars_folder_name)
    print get_all_files_with_pattern("*.jar", abs_path_dir + "/apks/")
    for f in get_all_files_with_pattern("*.jar", abs_path_dir + "/apks/"):
        conver_to_read_jar(f, read_jars_folder_name)


def unpack_all_jars_to_projects():
    files = get_all_files_with_pattern("*.jar", read_jars_folder_name)
    for f in files:
        path = "unzip " + read_jars_folder_name + "/" + \
            f + " -d " + abs_path_dir + "/projects/" + f[:-4]
        os.system(path)


def global_convert_to_projects():
    convert_all_apk_to_jar()
    convert_all_jar_to_read_jar()
    unpack_all_jars_to_projects()
    analysis_all_projects()


def project_analysis(folder):
    path_to_pmd = abs_path_dir + "/pmd/bin/"
    command = path_to_pmd + "run.sh pmd -d " + folder + \
        " -f text -R rulesets/java/basic.xml,rulesets/java/android.xml -version 1.7 -language java"
    status, output = commands.getstatusoutput(command)
    text_file_errors.write(output)


def analysis_all_projects():
    dir_name_proj = abs_path_dir + "/projects/"
    for folder in os.listdir(dir_name_proj):
        project_analysis(dir_name_proj + folder)
    text_file_errors.close()


def get_end_words(x, i):
    answer = ""
    for i in xrange(int(i), len(x)):
        answer += x[int(i)] + " "
    return answer


def delete_small_values(d):
    b = {}
    for k, v in d.iteritems():
        if v > 2:
            b[k] = v
    return b


def get_str_dict(d):
    s = ""
    for k, v in d.iteritems():
        s += str(k) + " - " + str(v) + "\n"
    return s


def draw_graph(d):
    d = delete_small_values(d)
    d = collections.OrderedDict(sorted(d.items()))
    text_statistic = open("statistic.txt", "w")
    text_statistic.write(get_str_dict(d))
    text_statistic.close()
    plt.bar(range(len(d)), d.values(), align='center')
    plt.xticks(range(len(d)), d.keys())
    plt.show()


def get_all_errors():
    strings = open("all_errors", "r+")
    warnings = {}
    for string in strings.readlines():
        x = string.split()
        key = ""
        if len(x[1]) > 2:
            key = get_end_words(x, 1)
        else:
            key = get_end_words(x, 2)

        if key in warnings:
            warnings[key] += 1
        else:
            warnings[key] = 1
    # print warnings
    return warnings


def main():
    global_convert_to_projects()
    text_file_errors.close()
    draw_graph(get_all_errors())


if __name__ == '__main__':
    main()



После работы скрипта, мы получим как статистику в текстовом виде, так и диаграмму(диаграмма строится при помощи самого популярного средства под Python — matplotlib). 


6. Что же все — таки получилось? 


А вышло примерно следующее: 


Avoid empty synchronized blocks — 2099

Avoid instantiating Boolean objects; reference Boolean.TRUE or Boolean.FALSE or call Boolean.valueOf() instead. — 269
Avoid using a branching statement as the last in a loop. — 170
Dont create instances of already existing BigInteger and BigDecimal (ZERO, ONE, TEN) — 10
Empty initializer was found — 41
Empty static initializer was found — 41
Ensure you override both equals() and hashCode() — 149
Invoke equals() on the object you've already ensured is not null — 9
Overriding method merely calls super — 310
The null check here is misplaced; if the variable is null there will be a NullPointerException — 10
These nested if statements could be combined — 22
Unnecessary final modifier in final class — 7501
Useless parentheses. — 3582
An empty statement (semicolon) not part of a loop — 4144
Do not hard code the IP address — 101
Do not hardcode /sdcard. — 11
Do not use if statements that are always true or always false — 21
An operation on an Immutable object (String, BigDecimal or BigInteger) wont change the object itself — 4
super should be called at the end of the method — 72
super should be called at the start of the method — 86

image


Выводы можете делать сами. Мой же вывод такой:

1. PMD достоин того, чтобы использовать его повсеместно, так как таким образом можно избежать не только дублирование кода и создание неиспользуемых переменных, но и выявить более серьезные ошибки. 
2. Прогоняйте ваш код через обфускатор. Желательно всегда.

Ссылки:

PMD: pmd

dex2jar: dex2jar

четверг, 7 ноября 2013 г.

суббота, 10 августа 2013 г.

Немного о ТEX'e и русском языке

Начал я читать книжку по ТЕХ'у и понял, что примеры с русским языком не пашут, уж не знаю почему. Гуглил достаточно долго. Нашел исходник(или как там это в ТЕХ называется?). Все как всегда оказалось просто.

\usepackage[utf8x]{inputenc}
\usepackage[english, russian]{babel}

Система у меня Linux Mint 15. Ищите ответы, и точно найдете :)

пятница, 24 мая 2013 г.

SCALA для нетерпеливых


Хочу поделиться частичкой радости. Вышла и уже пришла книга, которую многие очень долго ждали. В скором времени поделюсь впечатлениями, а может быть и небольшими частичками кода :)

Сервер за 5 баксов или Jetty 6 на Ubuntu

Здесь и сейчас я попытаюсь восстановить полную последовательность действий от взноса денег за сервак на digitalocean, до развертывания полноценного сайта.


1. Регистрируемся на сайте
digitalocean.com. Вносим деньги и выбираем конфигурацию машины. Я выбрал ubuntu 12.04. Сразу же отмечу, что машина имеет несколько оконных менеджеров, но так как у меня была минимальная конфигурация машины, то я решил поставить самый легковесный менеджер - openbox. Поэтому apt-get install openbox.

2. Теперь необходимо поставить jetty. Через apt-get ставится 6 версия(с ней проблем нет). Если ставить более позднюю версию, при деплое war'a начинаются косяки, которые вроде как должны решаться удалением записи "-- demon" в shell-скрипте, отвечающем за запуск  jetty, но нифига что-то не пашет. Поэтому остановимся на шестой версии jetty.


3. Подробнее о деплое. Во-первых, необходимо указать необходимые настройки для jetty. Для этого  подправим файлик  /etc/default/jettyПосле правки он должен выглядеть примерно так:
# change to 0 to allow Jetty to startNO_START=0# change to 'no' or uncomment to use the default setting in /etc/default/rcSVERBOSE=yes# Run Jetty as this user ID (default: jetty)# Set this to an empty string to prevent Jetty from starting automatically#JETTY_USER=jettyJETTY_HOST=$(uname -n)JETTY_HOST=0.0.0.0# The network port used by JettyJETTY_PORT=8999
 Для того, чтобы стартануть jetty нужно написать sudo /etc/init.d/jetty start(если не вводить start, и нажать enter, то выведутся все доступные команды). Во - вторых, для деплоя нужно положить WAR файл в папку /usr/share/jetty/webapps, перезапустить jetty-сервер, а также сделать java -jar start


4. Теперь немного о mySQL server'e. Ставится стандартно apt-get install. 3306 - стандартный порт, через который работает mySQL. При установке записываем пароли :)  


5. После установки mySQL, он пропускает только localhost. Нам необходимо прописать привилегии для ip-адресов, с которых будет вестись работа. Делается это следующими командами:
$ mysql -u root -pEnter password:mysql> use mysqlmysql> GRANT ALL ON *.* to root@'192.168.1.4' IDENTIFIED BY 'your-root-password';mysql> FLUSH PRIVILEGES;
6. А теперь о vnc. Установку сервера производил по мануалу http://www.prolinux.org/node/106.
Собственно для использования необходимо сначала зайти по ssh, запустить vnc4server, а потом спокойненько работать.



четверг, 14 марта 2013 г.

Scons

Scons - это замена makefile. Scons написан на Python. Scons очень удобен и у него замечательная документация. Scons полезен всем :)

Простой пример:
Допустим есть пример, состоящий всего из файлов main.cpp, 1.h, 1.cpp,  2.h,  2.cpp. Необходимо написать простейший сборщик.
Создаем файл SConstruct, почему название именно такое? Потому что оно дается автоматически, как Makefile, только SConstruct :) Затем пишем две строчки внутри файла:
env=Environment()
env.Program('myprog', ['main.cpp',1.cpp','2.cpp'] )

Пишем в консольке scons...
Scons дьявольски умен и сообразителен. И нам и ему понятно, что myprog  и есть наш собранный исполняемый файл.
Main page of scons

Ну а еще можно добавить, что scons используют:





пятница, 25 января 2013 г.

Пример работы с ftplib.

Еще один пост любви к Python. Как передать некоторый список файлов(имеются полные пути) на ftp-сервер? Очень просто :) 


server_ftp = '192.168.0.102' #для примера, в программе можно задать что угодно
login_ftp = "fox100500"
pass_ftp = "fox_134342"
port_ftp = "1069"


def ftp_online():
    ftpConnect = FTP()
    try:
        ftpConnect.connect(server_ftp, port_ftp)
        ftpConnect.login(login_ftp, pass_ftp)
        ftpConnect.quit()
        ftpConnect.close()
        enable = 1
    except:
        enable = 0
    return enable


def copy_file(ftp, path):  # ftp - через что соединяемся, path - абсолютный путь до передаваемого файла
    name_file = path.split('/')[-1:][0]
    send_file = open(path, 'rb')
    ftp.storbinary('STOR ' + name_file, send_file, 1024)


def read_ways_from_file(file_name):
    f = open(file_name, 'r')
    return f.readlines()


def copy_all_files(ftp, lines):  # ftp - через что соединяемся, lines - list абсолютных путей до файлов
    for line in lines:
        line = line.replace('\n', '')
        copy_file(ftp, line)


if __name__ == "__main__":

    if ftp_online() == 1:
        ftpConnect = FTP()
        ftpConnect.connect(server_ftp, port_ftp)
        ftpConnect.login(login_ftp, pass_ftp)
        ftpConnect.cwd('/information')
        st = ftpConnect.pwd()

        copy_all_files(ftpConnect, read_ways_from_file('download.txt'))

Есть некий текстовый файл download.txt, в котором записаны пути до нужных файлов. Нам лишь необходимо запустить скрипт и он скопирует эти файлы на сервак. 

Для чего это может использоваться? Ответ прост - отложенная отправка файлов на android. Есть куча программ, которые превращают андрюшу в сервер, также есть всячиские task менеджеры, которые могут включать и выключать wifi в определенное время, то есть нам лишь необходимо настроить запуск wifi андроида ночью, настроить запуск скрипта - граббера информации(скачка хабра, новостей, запись радио), а потом взять и передать все с помощью данного скриптика. 
Также хочу отметить, что для скачки контента из интерната лучше всего пользоваться wget. Помимо регулировки порядка вложенности скачки страниц он имеет кучу настроек. Допустим, чтобы скачать web-страницу полностью надо ввести в консоль:  wget -k -p <адрес страницы>.  


воскресенье, 13 января 2013 г.

Метод "Бегунка" на Java.

Хочется рассказать о решении классической задачи на списки. Многие знают что такое список и даже с чем его едят, но почему-то затем их забывают. Иногда же полезно вникнуть в суть и порешать задачи, решение которых может и не придти сразу в голову :)
Итак, дан список, нам необходимо определить содержит ли он петлю. Самый простой и действенный способ - воспользоваться так называемым методом бегунка.По списку бежит два бегунка - быстрый и медленный, пусть первый делает два шага за такт, а второй один. Допустим, что нам также известна длина списка = k. Число же шагов до петли пусть будет = l. Тогда если быстрый бегунок на l - k шагов отстает от медленного, а быстрый нагоняет его за 1 шаг в единицу времени, то они встретятся на l - k шагов. В этой самой точке они будут стоять как раз в k шагов от начала петли. Т.е. когда указатели встретятся, нам нужно передвинуть медленный бегунок на голову, а быстрый оставить на месте, при движении их с теми же скоростями место их следующей встречи и будет началом петли :)

Вот такой вот простой алгоритмик :) Как всегда исходник(Java): Исходник

Минимальная настойка linux-окружения на примере openbox.

Сейчас я сделаю то, что обязан сделать любой линуксоид - рассказать про свою минимальную настройку "linux-окружения". Конкретно хочу описать связку, которая, по-моему, работает замечательно = Openbox + conky + tint2 panel + pytyle2 + немного умения разбираться в конфигах. Если о первой составляющей слышали все, о второй чуть поменьше, о третьей еще меньше, то о четвертой просто необходимо рассказать подробнее. 

CONKY

Про конфигурацию коньков я уже рассказывал, ничего не изменилось. Я лишь написал еще один небольшой скрипт, который парсит RSS-ленту для получения погоды. Он будет также выложен(осторожно, говнокод!).

OPENBOX 

http://ru.wikipedia.org/wiki/Openbox. В общем все понятно. Ставим как обычно, выбираем при загрузке и наслаждаемся минимализмом и быстротой(если нифига не понятно и страшно -нажмите правую кнопку мыши : -) ).

TINT2 panel

Лютая, бешеная, простая в конфигурации, легкая панель. Запускается командой tint2
Ставится как обычно apt-get'oм. При первом запуске создастся конфигурационный файл в ~/.config/tint2/tint2rc. Его-то править и надо для достижения необходимого эффекта. 
Мне всегда хотелось, чтобы приложения, которые открыты на нескольких рабочих столах отображались на всех рабочих столах. После прочтения доков и правки конфигов мечта стала реальностью -  панель великолепна и удовлетворяет моим скромным требованиям. 


PyTYLE2

Скажу, что искал что-то, что помогло бы мне с тайлингом и написано бы было предпочтительно на python'e. Обнаружился pytyle2 - великолепная штучка, которую рекомендую всем :) Кстати, на использование ее меня натолкнул пост http://muhas.ru/?p=171(там же описаны комбинации клавиш по умолчанию и что где лежит). А вот небольшой примерчик тайлинга в действии. 

НЕМНОГО О НАСТРОЙКЕ

Автозагрузка
В ~/.config/openbox есть файлик с говорящим названием - autostart, если нет, то смело создаем. В нем прописываем все, что должно стартовать вместе с openbox. Описывается он примерно так:
tint2 &
pytyle2 &
conky &

Обои на рабочий стол
По умолчанию на рабочем столе ничего нет. Так просто все не изменить, есть несколько программ, которые занимаются сменой обоев, самая простая и действенная - feh. Ставим, прописываем feh --bg-scale "путь до обоины". И да, если хотите, чтобы обои были при запуске системы, пропишите команду в автозапуск.

Комбинации клавиш или как сделать скриншот экрана.

В ~/.config/openbox/rc.xml лежат настройки не только кнопок, но и многие настройки внешнего вида. По примеру того, как там описаны уже существующие комбинации для PrtSc пропишем запуск баш-скрипта:
$ cat screenshot.sh 
#!/bin/bash
name=`date +%x-%X-%N.png`
scrot ~/$name
Не забываем поставить scrot, именно она будет отвечать за "производство" скриншота. 

понедельник, 7 января 2013 г.

Быстрое добавление композиций в last.fm из vk.com

Я все как-то обходил вниманием last.fm, слушаю то пандору, то просто 42.fm. Но last.fm хороша тем, что безупречно подбирает что-то, что будет точно  Вам по вкусу :). По этому было решено "перенести" музыку из vk.com на last.fm. Так как композиций на данный момент у меня в профиле vkонтакта 445, то делать это в ручную как-то даже не прилично.
Был написан маленький скриптик -  скробблер vk => last.fm.

Опишу особенности реализации. Чтобы воспользоваться api last.fm необходимо получить две строки - ключ для api и некое секретное значение(ну а еще быть там зарегистрированным ;)) ).
Проходим по адресу http://www.lastfm.ru/api/accounts, регистрируем свое приложение, запоминаем строки.
Идем на http://code.google.com/p/pylast/, эсвээним либу(если нет svn - ставим). Сразу же напишем небольшой пример - добавление композиции.


import pylast
API_KEY = "your_key"
API_SECRET = "your_secret"

username = "username"
password_hash = pylast.md5("password")

network = pylast.LastFMNetwork(api_key = API_KEY, api_secret =
    API_SECRET, username = username, password_hash = password_hash)
library = pylast.Library(user = "username", network = network)
track = network.get_track("Rob Dougan", "Clubbed To Death (Kurayamino Variation)")
library.add_track(track)

И видим... Видим ошибку:

Все потому, что авторы библиотеки забыли добавить одну  строчку, из-за чего собственно и происходит баг. Потому идем в исходники библиотеки, а именно на строку примерно 1970(функция add_track() ), добавляем в нее строку params['artist'] = track.get_artist().get_name(). 
Запускаем пример снова... Работает!

Полдела сделано. Теперь ставим либу для работы с vk.com(sudo pip install vkontakte). Как заводить приложение мною уже было рассказано, 

да и вообще как получать и качать музыку тоже, поэтому расписывать смысла нет, 
используя старые наработки для каждой композиции из вконтакте получаем "имя
 исполнителя", "название" иотправляем в last.fm. После всего получаем забитый профиль :) 

Ссылка на скрипт как всегда: https://www.dropbox.com/s/zlqpu4p0q4t32qd/pyLastFM.py