@ -5,13 +5,12 @@
# LICENSE file in the root directory of this source tree. An additional grant
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.
# of patent rights can be found in the PATENTS file in the same directory.
import itertools
import logging
import logging
import os
import os
import util
import util
import tempfile
import tempfile
from inferlib import config , jwlib , utils
MODULE_NAME = __name__
MODULE_NAME = __name__
MODULE_DESCRIPTION = ''' Run analysis of code built with a command like:
MODULE_DESCRIPTION = ''' Run analysis of code built with a command like:
gradle [ options ] [ task ]
gradle [ options ] [ task ]
@ -31,9 +30,136 @@ def gen_instance(*args):
create_argparser = util . base_argparser ( MODULE_DESCRIPTION , MODULE_NAME )
create_argparser = util . base_argparser ( MODULE_DESCRIPTION , MODULE_NAME )
def extract_filepath ( parts ) :
size = len ( parts )
pos = size - 1
while pos > = 0 :
path = ' ' . join ( itertools . islice ( parts , pos , None ) )
if os . path . isfile ( path ) :
return parts [ : pos ] , path
pos - = 1
return parts , None
def pop ( the_list ) :
if len ( the_list ) > 0 :
return the_list . pop ( )
return None
def extract_argfiles_from_rev ( javac_arguments ) :
""" Extract class names and @argfiles from the reversed list. """
# Reverse the list, so it's in a natural order now
javac_arguments = list ( reversed ( javac_arguments ) )
java_opts = [ ]
saved = [ ]
java_arg = pop ( javac_arguments )
while java_arg :
if java_arg . startswith ( ' @ ' ) :
# Probably got an @argfile
path = ' ' . join ( [ java_arg [ 1 : ] ] + saved )
if os . path . isfile ( path ) :
java_opts . insert ( 0 , ' @ ' + path )
saved = [ ]
else :
# @ at the middle of the path
saved . insert ( 0 , java_arg )
else :
# Either a class name or a part of the @argfile path
saved . insert ( 0 , java_arg )
java_arg = pop ( javac_arguments )
# Only class names left
java_opts [ 0 : 0 ] = saved
return java_opts
# Please run the doctests using:
# $ python -m doctest -v gradle.py
def extract_all ( javac_arguments ) :
""" Extract Java filenames and Javac options from the Javac arguments.
>> > os . path . isfile = lambda s : s [ 1 : ] . startswith ( ' path/to/ ' )
>> > extract_all ( [ ] )
{ ' files ' : [ ] , ' opts ' : [ ] }
>> > extract_all ( [ ' -opt1 ' , ' optval1 ' , ' /path/to/1.java ' ] )
{ ' files ' : [ ' /path/to/1.java ' ] , ' opts ' : [ ' -opt1 ' , ' optval1 ' ] }
>> > extract_all ( [ ' -opt1 ' , ' optval1 ' , ' /path/to/a ' , ' b/1.java ' ] )
{ ' files ' : [ ' /path/to/a b/1.java ' ] , ' opts ' : [ ' -opt1 ' , ' optval1 ' ] }
>> > extract_all ( [ ' -opt1 ' , ' opt ' , ' val1 ' , ' /path/to/1.java ' ] )
{ ' files ' : [ ' /path/to/1.java ' ] , ' opts ' : [ ' -opt1 ' , ' opt val1 ' ] }
>> > extract_all ( [ ' -opt1 ' , ' /path/to/a ' , ' b/c ' , ' d/1.java ' , ' -opt2 ' ] )
{ ' files ' : [ ' /path/to/a b/c d/1.java ' ] , ' opts ' : [ ' -opt1 ' , ' -opt2 ' ] }
>> > extract_all ( [ ' -opt1 ' , ' optval1 ' , ' -path/to/1.java ' ] )
{ ' files ' : [ ' -path/to/1.java ' ] , ' opts ' : [ ' -opt1 ' , ' optval1 ' ] }
>> > extract_all ( [ ' -opt1 ' , ' optval1 ' , ' /path/to/ ' , ' -1.java ' ] )
{ ' files ' : [ ' /path/to/ -1.java ' ] , ' opts ' : [ ' -opt1 ' , ' optval1 ' ] }
>> > extract_all ( [ ' undef1 ' , ' undef2 ' ] )
{ ' files ' : [ ] , ' opts ' : [ ' undef1 ' , ' undef2 ' ] }
>> > extract_all ( [ ' -o ' , ' /path/to/1.java ' , ' cls.class ' , ' @/path/to/1 ' ] )
{ ' files ' : [ ' /path/to/1.java ' ] , ' opts ' : [ ' -o ' , ' cls.class ' , ' @/path/to/1 ' ] }
>> > extract_all ( [ ' -opt1 ' , ' optval1 ' , ' /path/to/1.java ' , ' cls.class ' ] )
{ ' files ' : [ ' /path/to/1.java ' ] , ' opts ' : [ ' -opt1 ' , ' optval1 ' , ' cls.class ' ] }
>> > extract_all ( [ ' cls.class ' , ' @/path/to/a ' , ' b.txt ' ] )
{ ' files ' : [ ] , ' opts ' : [ ' cls.class ' , ' @/path/to/a b.txt ' ] }
>> > extract_all ( [ ' cls.class ' , ' @/path/to/a ' , ' @b.txt ' ] )
{ ' files ' : [ ] , ' opts ' : [ ' cls.class ' , ' @/path/to/a @b.txt ' ] }
>> > v = extract_all ( [ ' -opt1 ' , ' optval1 ' ] * 1000 + [ ' /path/to/1.java ' ] )
>> > len ( v [ ' opts ' ] )
2000
"""
java_files = [ ]
java_opts = [ ]
# Reversed Javac options parameters
rev_opt_params = [ ]
java_arg = pop ( javac_arguments )
while java_arg :
if java_arg . endswith ( ' .java ' ) :
# Probably got a file
remainder , path = extract_filepath ( javac_arguments + [ java_arg ] )
if path is not None :
java_files . append ( path )
javac_arguments = remainder
# The file name can't be in the middle of the option
java_opts . extend ( extract_argfiles_from_rev ( rev_opt_params ) )
rev_opt_params = [ ]
else :
# A use-case here: *.java dir as an option parameter
rev_opt_params . append ( java_arg )
elif java_arg . startswith ( ' - ' ) :
# Got a Javac option
option = [ java_arg ]
if len ( rev_opt_params ) > 0 :
option . append ( ' ' . join ( reversed ( rev_opt_params ) ) )
rev_opt_params = [ ]
java_opts [ 0 : 0 ] = option
else :
# Got Javac option parameter
rev_opt_params . append ( java_arg )
java_arg = pop ( javac_arguments )
# We may have class names and @argfiles besides java files and options
java_opts . extend ( extract_argfiles_from_rev ( rev_opt_params ) )
return { ' files ' : java_files , ' opts ' : java_opts }
def normalize ( path ) :
from inferlib import utils
# From Javac docs: If a filename contains embedded spaces,
# put the whole filename in double quotes
quoted_path = path
if ' ' in path :
quoted_path = ' " ' + path + ' " '
return utils . encode ( quoted_path )
class GradleCapture :
class GradleCapture :
def __init__ ( self , args , cmd ) :
def __init__ ( self , args , cmd ) :
from inferlib import config , utils
self . args = args
self . args = args
# TODO: make the extraction of targets smarter
# TODO: make the extraction of targets smarter
self . build_cmd = [ cmd [ 0 ] , ' --debug ' ] + cmd [ 1 : ]
self . build_cmd = [ cmd [ 0 ] , ' --debug ' ] + cmd [ 1 : ]
@ -46,6 +172,8 @@ class GradleCapture:
logging . info ( ' Running with: \n ' + utils . decode ( version_str ) )
logging . info ( ' Running with: \n ' + utils . decode ( version_str ) )
def get_infer_commands ( self , verbose_output ) :
def get_infer_commands ( self , verbose_output ) :
from inferlib import config , jwlib
argument_start_pattern = ' Compiler arguments: '
argument_start_pattern = ' Compiler arguments: '
calls = [ ]
calls = [ ]
seen_build_cmds = set ( [ ] )
seen_build_cmds = set ( [ ] )
@ -59,14 +187,12 @@ class GradleCapture:
if build_agnostic_cmd in seen_build_cmds :
if build_agnostic_cmd in seen_build_cmds :
continue
continue
seen_build_cmds . add ( build_agnostic_cmd )
seen_build_cmds . add ( build_agnostic_cmd )
javac_arguments = content . split ( ' ' )
# Filter out the empty elements
java_files = [ ]
arguments = list ( filter ( None , content . split ( ' ' ) ) )
java_args = [ ]
extracted = extract_all ( arguments )
for java_arg in javac_arguments :
java_files = extracted [ ' files ' ]
if java_arg . endswith ( ' .java ' ) :
java_args = extracted [ ' opts ' ]
java_files . append ( java_arg )
else :
java_args . append ( java_arg )
with tempfile . NamedTemporaryFile (
with tempfile . NamedTemporaryFile (
mode = ' w ' ,
mode = ' w ' ,
suffix = ' .txt ' ,
suffix = ' .txt ' ,
@ -74,7 +200,7 @@ class GradleCapture:
dir = os . path . join ( self . args . infer_out ,
dir = os . path . join ( self . args . infer_out ,
config . JAVAC_FILELISTS_FILENAME ) ,
config . JAVAC_FILELISTS_FILENAME ) ,
delete = False ) as sources :
delete = False ) as sources :
sources . write ( ' \n ' . join ( map ( utils. encod e, java_files ) ) )
sources . write ( ' \n ' . join ( map ( normaliz e, java_files ) ) )
sources . flush ( )
sources . flush ( )
java_args . append ( ' @ ' + sources . name )
java_args . append ( ' @ ' + sources . name )
capture = jwlib . create_infer_command ( java_args )
capture = jwlib . create_infer_command ( java_args )