Create a gist now

Instantly share code, notes, and snippets.

@voluntas /luli.rst
Last active Jan 2, 2016

What would you like to do?
Lua 言語用ソースコード静的解析ツール

Lua 言語用ソースコード静的解析ツール

日時:2014-05-24
作:時雨堂
バージョン:1.1.10
url:http://shiguredo.jp/

Lua ソースコード静的解析ツールは 時雨堂 が開発し販売をしています

お知らせ

体験版

1ヶ月試用可能な体験版を提供しております、ご興味ある方はご連絡下さい。

お問い合わせ

このツールに興味がある方は以下までご連絡ください。

tel:03-6240-1490
mail:contact at shiguredo.jp

現在は Mac OS X 版または Linux 版を提供可能です。

提供環境

Mac OS X

  • 10.8.x
  • 10.9.x

Linux

  • CentOS 6.5
  • Ubuntu 12.04
  • Ubuntu 14.04

準備中

  • Windows 版の提供
  • vim プラグインの提供
  • emacs プラグインの提供

ライセンス

販売

  • 法人のみが対象
  • 年間ライセンス
  • アップデートなどは無料

ユーザライセンス

デスクトップなど「個人」向けのライセンス

  • エディタとの連携
  • ローカルでのソースコード変更後の自動チェック

基本ライセンス:

1 ユーザ 12,000 円 / 年

価格表:

 1 ユーザ  12,000 円 / 年
10 ユーザ 108,000 円 / 年

インストールライセンス

リポジトリサーバやビルドサーバなど「不特定多数」向けのライセンス

  • ビルドサーバへの組み込み
  • バージョン管理のコミットフック
1 OS に対して 1 インストール 600,000 円 / 年

価格表:

1 インストール   600,000 円 / 年

開発動機

最近 Lua という言語が周りでよく使われている気がします。

周りに話を聞く感じだと、以下のようなところで使われているようです。

  • Nginx + Lua
  • Redis + Lua
  • Wireshark + Lua
  • Cocos2dx + Lua

また、World of Warcraft や ファイナルファンタジーXIV といった MMORPG にも使われています。

文法がシンプルでとても高速なことから組み込み言語としての用途が多いようです。

時雨堂では仕事で開発するときは「コードの統一感」がとても重要だと考えています。 またコードレビューをする前に、ツールで探し出せる問題点は省くべきとも考えています。

つまり、コーディング規約をチェックしたりするツール (lint とも呼ばれている) が Lua には必要ではないだろうかと考えました。

また lua を使って開発していく上に以下のような問題を抱えました。

  • Lua はエラー原因がわかりにくい
    • 組み込み用途では、単純な文法エラーや typo にも気づきにくい
  • 存在しない変数や(テーブルの)フィールドにアクセスしても nil が返る
  • 関数の引数の数がまったく検査されない
    • 引数は省略可能、または定義以上に与えても何も問題ない

これらの問題を気軽にツールでチェック出来ないものだろうかと考えました。

実際いくつかツールは存在しましたが、我々の要件を完全に満たすものはありませんでした。

サンプル

何はともあれサンプルを見てください。

以下のコードはソースコードチェッカー開発用に作っている lua のテストスクリプトです。

-- 80 文字以上の行
-----------------------------------------------------------------------------------

-- グローバル変数
g = "hello"

-- 80 文字以上の行
-----------------------------------------------------------------------------------

local M = {}

-- 未使用の変数
local unused

-- グローバル関数
function add(a, b)
  -- 未定義の変数: "c"
  return a + b + c
end

-- ローカル関数
local function DoAnything()
end

-- リテラル引数 (関数との間にスペースがない)
ipairs {}
ipairs{}
ipairs "hello"
ipairs"hello"
ipairs 'hello'
ipairs'hello'

-- 演算子前後のスペース
local a=1
a=true
a = false -- ok
a   =    true
a = 1+1

-- 単項演算子
a = -1
a = -(a+1)

-- 文字列
local s
s = 'alo\n123"'
s = "alo\n123\""
s = '\97lo\10\04923"'
s = "\97lo\10\04923'"
s = 'a\0o a\0o a\0o'
s = "a\0o a\0o a\0o"
s = 'aa\x61aa'
s = "aa\x61aa"
s = "\a\b\f\n\r\t\v\\\'\""
s = '\a\b\f\n\r\t\v\\\'\"'
s = "[\"key\"]\n"
s = '[\'key\']\n'

-- 数値
local num
num = 3
num = 3.0
num = 3.1416
num = 314.16e-2
num = 0.31416E1
num = 0xff
num = 0x56
-- 以降 Lua 5.2 から
-- num = 0x0.1E
-- num = 0xA23p-4
-- num = 0X1.921FB54442D18P+1

-- テーブル
local t = {}
local v
t = { }
t = { key1 = 'value1', key2 = 'value2', [1 + 1] = 2 }
t = {key1='value1',key2='value2',[1+1]=2}
t = { v = v }
t = {1}
t = {1,2,3}
t = { 1, 2, 3 }
t = { 1, 2, 3, key = 4 }
print(t[0]) -- element 0
print(t[1])

-- ライブラリのロード (-r)
require "global"

-- グローバル変数の再代入
other_gvar = 0

-- if
if false then
  local v = "Hello"
  print(v .. ", world!")
elseif true then
  local v = "Hello"
  print(v..", world!")
else
  local v = "Hello"
  print(v..", world!")
end

-- 数値の for
for i=1,100,2 do
  local j
end

-- 汎用 for
for i,v in pairs {} do
  local j
end

-- ipairs の場合は先頭の引数を unused の検査対象にしない
for i, v in ipairs {} do
end

-- 括弧で囲まれた式
(ipairs{}).key = nil
( ipairs {} ).key = nil

-- エラーを無視する
ipairs"valid" -- luli: noqa
ipairs"invalid" -- luli: noqaaaa
ipairs"-- luli: noqa" -- 文字列中の指定は無効

-- 長いコメント
--[[
コメント
]]

-- 長い文字列を含む長いコメント
--[=[
[[長い文字列]]
]=]
--[===[
[[長い文字列]]
]===]

-- 長い文字列
print [[alo
123"]]
print [[alo
123"
]]
print [[alo
123"
]]
print [==[
alo
123"]==]
print[====[
alo
123"]====]

-- cocos2d オプション
local node = CCNode:create()
node:setPosition(ccp(0, 0))

-- クロージャ
local f = function (farg1, farg2, farg3)
  return farg1+farg2
end

-- インデント
if M then print("expected an indent block") end
if M then
    print("unexpected indentation")
  elseif M then -- unexpected indent
     print("not a multiple 2 spaces")
  else -- unexpected indent
           print("indentation contains mixed spaces and tabs")
end

-- グローバル変数の再代入
GVAR = "world"
print(GVAR)

print(other_var)
return M

このコードに対して Lua ソースコードチェッカーを実行すると以下のようなエラーが出ます。

実行結果は開発中のものです

$ luli test.lua
test.lua:4:1: W1 line too long (83 > 79 characters)
test.lua:7:1: W9 global variable definition `g'
test.lua:10:1: W1 line too long (83 > 79 characters)
test.lua:15:7: W5 unused variable `unused'
test.lua:18:10: W10 global function definition `add'
test.lua:18:10: W5 unused variable `add'
test.lua:20:18: W6 unassigned variable `c' (did you mean `a'?)
test.lua:24:16: W11 not snake case `DoAnything'
test.lua:24:16: W5 unused variable `DoAnything'
test.lua:29:7: W33 missing whitespace before literal argument
test.lua:31:7: W33 missing whitespace before literal argument
test.lua:33:7: W33 missing whitespace before literal argument
test.lua:36:8: W23 missing whitespace before `='
test.lua:37:2: W23 missing whitespace before `='
test.lua:39:5: W26 too much whitespace around `='
test.lua:40:6: W29 missing whitespace before `+'
test.lua:44:8: W29 missing whitespace before `+'
test.lua:47:7: W5 unused variable `s'
test.lua:62:7: W5 unused variable `num'
test.lua:78:5: W15 whitespace after `{'
test.lua:79:5: W15 whitespace after `{'
test.lua:80:10: W23 missing whitespace before `='
test.lua:80:24: W23 missing whitespace before `='
test.lua:80:36: W29 missing whitespace before `+'
test.lua:80:39: W23 missing whitespace before `='
test.lua:81:5: W15 whitespace after `{'
test.lua:84:5: W15 whitespace after `{'
test.lua:85:5: W15 whitespace after `{'
test.lua:86:9: W34 use of element 0 for array-like table access
test.lua:93:1: W9 global variable definition `other_gvar'
test.lua:96:4: W3 block may never be used
test.lua:99:8: W4 meaningless condition (block shall be run)
test.lua:101:10: W29 missing whitespace before `..'
test.lua:104:10: W29 missing whitespace before `..'
test.lua:108:5: W5 unused variable `i'
test.lua:108:6: W23 missing whitespace before `='
test.lua:108:8: W20 too much whitespace after comma
test.lua:108:12: W20 too much whitespace after comma
test.lua:109:9: W5 unused variable `j'
test.lua:113:6: W20 too much whitespace after comma
test.lua:113:7: W5 unused variable `v'
test.lua:114:9: W5 unused variable `j'
test.lua:118:8: W5 unused variable `v'
test.lua:122:8: W33 missing whitespace before literal argument
test.lua:123:1: W13 whitespace after `('
test.lua:123:13: W14 whitespace before `)'
test.lua:127:7: W33 missing whitespace before literal argument
test.lua:128:7: W33 missing whitespace before literal argument
test.lua:155:6: W33 missing whitespace before literal argument
test.lua:160:14: W6 unassigned variable `CCNode'
test.lua:161:18: W6 unassigned variable `ccp'
test.lua:164:7: W5 unused variable `f'
test.lua:164:35: W5 unused variable `farg3'
test.lua:165:15: W29 missing whitespace before `+'
test.lua:169:11: W112 expected an indented block
test.lua:171:5: W113 unexpected indentation
test.lua:172:3: W113 unexpected indentation
test.lua:173:6: W111 indentation is not a multiple of two spaces (2 spaces)
test.lua:174:3: W113 unexpected indentation
test.lua:175:5: W101 indentation contains mixed spaces and tabs
test.lua:179:1: W9 global variable definition `GVAR'
test.lua:182:7: W6 unassigned variable `other_var' (did you mean `other_gvar'?)
$ luli -h
luli: Lua static analysis tool by Shiguredo Inc.

  luli [FILENAME]

=== flags ===

  [-L]                library load path
  [-anon-args]        do not produce `unused variable' warnings for arguments
                      which name begins with `_'
  [-cocos]            check for cocos2d-x
  [-config]           config file. if this option not specified, luli tries to
                      find a directory that has "Lulifile"
  [-d]                debug output from parser
  [-first]            show first occurrence of each error
  [-ignore]           skip errors and warnings (e.g. E,W,W4)
  [-init]             create project config file (Lulifile) into the current
                      directory
  [-l]                load (require) the library before the script
  [-license]          print license information
  [-limit]            set maximum allowed errors and warnings
  [-lua-version]      target version of Lua [5.1, 5.2] (default: 5.1)
  [-max-line-length]  set maximum allowed line length (default: 79)
  [-no-autoload]      disable loading libraries specified by "require()" on top
                      level
  [-no-spell-check]   disable spell checking
  [-select]           select errors and warnings (e.g. E,W,W4)
  [-spot]             print analysis result at specified position that line and
                      column number (e.g. 12:34) (experimental)
  [-v]                print verbose message
  [-warn-error-all]   make all warnings into errors
  [-warn-error]       make warnings into errors (e.g. 4,37,123)
  [-build-info]       print info about this build and exit
  [-version]          print the version of this build and exit
  [-help]             print this help text and exit
                      (alias: -?)

-max-line-length と -ignore を指定した例

$ luli -max-line-length 120 -ignore W33,W29 test.lua
test.lua:7:1: W9 global variable definition `g'
test.lua:15:7: W5 unused variable `unused'
test.lua:18:10: W10 global function definition `add'
test.lua:18:10: W5 unused variable `add'
test.lua:20:18: W6 unassigned variable `c' (did you mean `a'?)
test.lua:24:16: W11 not snake case `DoAnything'
test.lua:24:16: W5 unused variable `DoAnything'
test.lua:36:8: W23 missing whitespace before `='
test.lua:37:2: W23 missing whitespace before `='
test.lua:39:5: W26 too much whitespace around `='
test.lua:47:7: W5 unused variable `s'
test.lua:62:7: W5 unused variable `num'
test.lua:78:5: W15 whitespace after `{'
test.lua:79:5: W15 whitespace after `{'
test.lua:80:10: W23 missing whitespace before `='
test.lua:80:24: W23 missing whitespace before `='
test.lua:80:39: W23 missing whitespace before `='
test.lua:81:5: W15 whitespace after `{'
test.lua:84:5: W15 whitespace after `{'
test.lua:85:5: W15 whitespace after `{'
test.lua:86:9: W34 use of element 0 for array-like table access
test.lua:93:1: W9 global variable definition `other_gvar'
test.lua:96:4: W3 block may never be used
test.lua:99:8: W4 meaningless condition (block shall be run)
test.lua:108:5: W5 unused variable `i'
test.lua:108:6: W23 missing whitespace before `='
test.lua:108:8: W20 too much whitespace after comma
test.lua:108:12: W20 too much whitespace after comma
test.lua:109:9: W5 unused variable `j'
test.lua:113:6: W20 too much whitespace after comma
test.lua:113:7: W5 unused variable `v'
test.lua:114:9: W5 unused variable `j'
test.lua:118:8: W5 unused variable `v'
test.lua:123:1: W13 whitespace after `('
test.lua:123:13: W14 whitespace before `)'
test.lua:160:14: W6 unassigned variable `CCNode'
test.lua:161:18: W6 unassigned variable `ccp'
test.lua:164:7: W5 unused variable `f'
test.lua:164:35: W5 unused variable `farg3'
test.lua:169:11: W112 expected an indented block
test.lua:171:5: W113 unexpected indentation
test.lua:172:3: W113 unexpected indentation
test.lua:173:6: W111 indentation is not a multiple of two spaces (2 spaces)
test.lua:174:3: W113 unexpected indentation
test.lua:175:5: W101 indentation contains mixed spaces and tabs
test.lua:179:1: W9 global variable definition `GVAR'
test.lua:182:7: W6 unassigned variable `other_var' (did you mean `other_gvar'?)

特定の行のエラーを完全に無視する仕組み

-- エラーを無視する
ipairs"valid" -- luli: noqa

Lulifile

Lulifile を使う事で引数に指定する必要がなくなります

$ luli -init
# creating Lulifile
$ cat Lulifile
[luli]
; デバッグモード
; debug = true

; 詳細メッセージの表示
; verbose = true

; Lua のバージョン (5.1, 5.2)
; lua-version = 5.2

; 指定したエラーコードのみを表示する
; select = E

; 指定したエラーコードを表示しない
; ignore = E, W
; ignore = E261, E701, W302, W303

; 一行の文字数
; max-line-length = 79

; cocos2d-x モード
; cocos = true

; 表示するエラーの最大数
; limit = 30

; 指定した警告をエラーとして扱う
; warn-error = W292, W293, W391

; すべての警告をエラーとして扱う
; warn-error-all = true

; スペルチェックを行わない
; spell-check = false

; ライブラリのロードパス
; L = /usr/lib/lua, /usr/local/lib/lua

; require で指定されているライブラリをロードする
; autoload = false

; 指定したライブラリを解析前にロードする
; l = mylib

; 検出されたそれぞれのエラーコードのうち、最初に現れた結果のみを表示する
; first = true

; 名前が '_' で始まる引数に対して未使用の警告を行わない
; anon-args = true

vim プラグイン

vim プラグインを提供する予定です。

ソースコードは公開予定です。

画像は開発中のものです

https://dl.dropboxusercontent.com/u/89936/gist/luli/luli_vim.png

emacs プラグイン

emacs のプラグインを提供する予定です。

ソースコードは公開予定です。

画像は開発中のものです

https://dl.dropboxusercontent.com/u/89936/gist/luli/luli_emacs.png

Xcode プラグイン

Xcode 用のプラグインを提供する予定です。

実験的機能

静的型解析機能

例は VIM プラグインを使って実装しました。

ビルトイン関数定義をリアルタイムに確認出来ます

string.format の型

https://dl.dropboxusercontent.com/u/89936/gist/luli/1.png

ipairs の型

https://dl.dropboxusercontent.com/u/89936/gist/luli/2.png

定数の中身をリアルタイムに確認出来ます

https://dl.dropboxusercontent.com/u/89936/gist/luli/3.png

ユーザ定義関数をリアルタイムに確認出来ます

https://dl.dropboxusercontent.com/u/89936/gist/luli/4.png

機能候補

以下のリストは今後の実装内容を保証するものではありません

コーディングスタイル

  • インデントのサイズ、タブまたはスペース
  • インデントの揃え
  • ブロックのインデント検出 (new)
  • タブとスペースが混在しているか
  • 変数名、関数名、メソッド名のフォーマット: snake, camel, または正規表現
  • 一行の文字数
  • 関数の行数
  • 識別子の長さ
  • 代入記号前後のスペース
  • 関数呼び出しのカッコの前後のスペース
  • 単一のリテラルを引数に取る関数呼び出しのスペース
  • 関数呼び出しの引数の前後のスペース、またはカンマの次のスペース
  • テーブル生成のキーのスペース
  • テーブル要素のアクセスの前後のスペース
  • 演算子前後のスペース

一般

  • 未定義の変数を参照している (標準ライブラリかチェック)
  • グローバル変数を定義している
  • 未使用のローカル変数がある
  • 未使用の関数がある
  • 標準ライブラリにない変数を参照している (string.xxx など)
  • 定義するローカル変数名が組み込みの変数名と重複している
  • 組み込み変数、関数に代入している
  • 実行されないブロック: if, while
  • 意味のない文: リテラルのみなど
  • 関数に与える引数が多い (確実にわかる場合のみ)
  • require の戻り値があるかどうか
  • テーブルのインデックスに 0 を指定すると警告 (new)
  • スペルミスの候補 (new)

モジュール

  • グローバル変数が定義されている
  • グローバル関数が定義されている
  • ファイル末尾でモジュールオブジェクトが返されていない
  • モジュール関数が一つも定義されていない

開発

このツールは OCaml で開発しています。

サポーター

  • TAMURA YUKI
    • このツールを作るきっかけを頂きました。
    • アルファ版のテストに協力頂いています。
  • FURUSE JUN
    • OCaml に関するアドバイスを頂いています
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment