Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
#!/usr/bin/tclsh
# Copyright (C) 2007 Salvatore Sanfilippo <antirez at gmail dot com>
# This software is released under the GPL license version 2
proc scan file {
set fd [open $file]
set infunc 0
set linenr 0
set fnre {(^\s*)((public|private|protected|static)\s*)*function\s+([^(]+)\s*\((.*)\).*}
while {[gets $fd line] != -1} {
incr linenr
if {[regexp $fnre $line - ind - - - fa]} {
# If $infunc is true we miss the end of the last function
# so we analyze it now.
if {$infunc} {
analyze $file $arglist $body
}
set body {}
set arglist {}
foreach arg [split $fa ,] {
# remove default value
regsub {=.*} $arg {} arg
# remove optional type spec
regsub {^.*\s+} [string trim $arg] {} arg
set arg [string trim $arg " $&"]
lappend arglist $arg
}
set infunc 1
} elseif {$infunc && [regexp "^$ind\}" $line]} {
set infunc 0
analyze $file $arglist $body
} elseif {$infunc} {
lappend body $linenr [string trim $line]
}
}
}
proc analyze {file arglist body} {
set initialized(this) 1
set linton 1
foreach arg $arglist {
set initialized($arg) 1
}
# Superglobals
set superglobals {
"GLOBALS"
"_SESSION"
"_SESSION"
"_GET"
"_POST"
"_REQUEST"
"_ENV"
"_SERVER"
"_FILES"
"php_errormsg"
}
foreach sg $superglobals {
set initialized($sg) 1
}
# analyze body
foreach {linenr line} $body {
# Handle annotations
if {[string first {nolint} [string tolower $line]] != -1} continue
if {[string first {linton} [string tolower $line]] != -1} {
if {$linton == 1} {
puts "! Warning 'linton' annotation with lint already ON"
continue
}
set linton 1
puts ". $skipped lines skipped in $file from line $skipstart"
}
if {[string first {lintoff} [string tolower $line]] != -1} {
if {$linton == 0} {
puts "! Warning 'lintoff' annotation with lint already OFF"
continue
}
set linton 0
set skipped 0
set skipstart [expr {$linenr+1}]
continue
}
if {$linton == 0} {
incr skipped
continue
}
# Skip comments
if {[string index $line 0] eq {#}} continue
if {[string index $line 0] eq {/} && [string index $line 1] eq {/}} continue
# PHP variable regexp
set varre {\$[_A-Za-z]+[_A-Za-z0-9]*(\[[^\]]*\])*}
# Check for globals
set re {\s*(global|static)\s+((?:\$[^;,]+[ ,]*)+)(;|$)}
if {[regexp $re $line -> - g]} {
set g [split [string trim $g ";"] ,]
foreach v $g {
set v [string trim $v "$ "]
set initialized($v) 1
}
}
# Check for assignment via foreach ... as &$varname
set re {}
append re {foreach\s*\(.*\s+as\s+&?(} $varre {)\s*\)}
set l [regexp -all -inline -nocase $re $line]
foreach {- a -} $l {
set initialized([string trim $a "$ "]) 1
}
# Check for assignment via foreach ... as $key => &$val
set re {}
append re {foreach\s*\(.*\s+as\s+(} $varre {)\s*=>\s*&?(} $varre {)\s*\)}
set l [regexp -all -inline -nocase $re $line]
foreach {- a1 - a2 -} $l {
set initialized([string trim $a1 "$ "]) 1
set initialized([string trim $a2 "$ "]) 1
}
# Check for assigments in the form list($a,$b,$c) = ...
set re {list\s*\(([^=]*)\)\s*=}
set l [regexp -all -inline $re $line]
foreach {- vars} $l {
foreach v [split $vars ,] {
set v [string trim $v "$ "]
set initialized($v) 1
}
}
# Check for assigments via = operator
set re $varre
append re {\s*=}
set l [regexp -all -inline $re $line]
foreach {a -} $l {
set a [string trim $a "=$ "]
regsub -all {\[.*\]} $a {[]} a
#puts "assigmnent of $a"
set initialized($a) 1
regsub -all {\[\]} $a {} a
set initialized($a) 1
}
# Check for assignments via catch(Exception $e)
set re {}
append re {catch\s*\(.*\s+(} $varre {)}
set l [regexp -all -inline -nocase $re $line]
foreach {- a} $l {
set initialized([string trim $a "$ "]) 1
}
# Check for assignments by reference
#
# funclist format is {type funcname spos epos} where spos is the
# zero-based index of the first argument that can be considered
# an assignment, while epos is the last.
#
# name is the function name to match, and type is what
# to do with the args. "assignment" to consider them assigned
# or "ingore" to ingore them for the current line.
#
# The "ignore" is used for isset() and other functions that can
# deal with not initialized vars.
unset -nocomplain -- ignore
array set ignore {}
set funclist {
assignment scanf 2 100
assignment preg_match 2 100
assignment preg_match_all 2 100
assignment ereg 2 100
ignore isset 0 0
}
set cline $line
regsub -all {'[^']+'} $cline {''} cline
foreach {type name spos epos} $funclist {
set re {}
append re $name {\s*\(([^()]*)\)}
foreach {- fargs} [regexp -all -inline $re $cline] {
set argidx 0
foreach a [split $fargs ,] {
set a [string trim $a ", $"]
regsub -all {\[.*\]} $a {} a
if {$argidx >= $spos && $argidx <= $epos} {
if {$type eq {assignment}} {
set initialized($a) 1
} elseif {$type eq {ignore}} {
set ignore($a) 1
}
}
incr argidx
}
}
}
# Check for var accesses
set varsimplere {\$[_A-Za-z]+[_A-Za-z0-9]*}
set l [regexp -all -inline $varsimplere $line]
foreach a $l {
set a [string trim $a "=$ "]
regsub -all {\[.*\]} $a {} a
#puts "access of $a"
if {![info exists initialized($a)] &&
![info exists ignore($a)]} {
puts "* In $file line $linenr: access to uninitialized var '$a'"
}
}
}
}
proc main argv {
foreach file $argv {
scan $file
}
}
main $argv