/* * @(#)JavaFilter.java 1.27 06/10/10 * * Copyright 1990-2008 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. * */ import util.*; import jcc.*; import java.util.Enumeration; import java.util.Vector; import dependenceAnalyzer.*; import java.io.PrintStream; import java.io.FileOutputStream; class JavaFilter extends DepgenUtil{ boolean hadError = false; int verbosity= 0; /* * Java linking. * The member linker determines which class members * are required to satisfy the references of the * members named, taking into account the problems * introduced by method overriding and references from * unanalysed sources, as directed by command line options. * * The member linker works off a list of things that * must be included, iteratively analysing the members * on this list, and building a new list of required * members. * * This operation is controlled by a number of command-line * options: * -classpath and -exclude, as decribed below. * * -load full-member-name * to put the named member on the work list. * -loadClass full-classname * to put all members of the named class on the * work list. This is especially useful for classes * that will be used by dynamically-loaded code * in ways that cannot be analysed at this time. * -interface full-classname * to indicate that the named class, whether a * Java class or a Java interface, defines an * interface contract which must be met by its * subclasses, even if the named class, itself * is excluded from the load. This is especially * useful when packaging up something, such as * a subclass of java.lang.Applet, for dynamic * loading, and it must meet the named interface * contract. */ /* * These are the flags that control member loading: * REQ -- the marked element is required to run. Either * it was specified with -load or -loadClass, or * was determined to be necessary for something * that was. * INITREQ -- constructor required. Marks a class if * at least one of its constructors is marked REQ. * INTF -- interface. Marks a member of a class specified * with -interface. * LISTED -- just a tag for members listed by the user * as needing loading. Either as individuals or * as a member of a listed class. */ static final int REQ =(1<<29); static final int INITREQ=(1<<28); static final int INTF =(1<<27); static final int LISTED =(1<<26); Vector initialList = new Vector(); PrintStream nativelist = null; private void loadNode( DependenceNode node ){ node.flags |= LISTED; initialList.addElement( node ); } /* * loadMember -- triggered by the command-line option * -load full-member-name */ private void loadMember( String membername ){ MemberDependenceNode node = stringToNode( membername ); if ( node == null ){ System.out.println( Localizer.getString("javafilter.cannot_find_member", membername)); hadError = true; return; } loadNode( node ); loadNode( ((MemberName)(node.name())).classEntry ); } /* * loadConstructor -- triggered by the command-line option * -new full-class-name or * -new full-class-name(signature)V */ private void loadConstructor( String constructorname ){ MemberDependenceNode node = stringToConstructorNode( constructorname ); if ( node == null ){ System.out.println( Localizer.getString("javafilter.cannot_find_constructor", constructorname) ); hadError = true; return; } loadNode( node ); loadNode( ((MemberName)(node.name())).classEntry ); } /* * loadClass -- triggered by the command-line option * -loadClass full-classname */ private void loadClass( String classname ){ ClassEntry c = stringToClass( classname ); if ( c == null ){ System.out.println( Localizer.getString("javafilter.cannot_find_class", classname) ); hadError = true; return; } loadNode( c ); allClassMembers( classname ); } /* * interfaceClass -- triggered by the command-line option * -interface full-classname */ private boolean interfaceClass( String classname ){ if ( ! this.filter.accept( null, sanitizeClassname( classname ) ) ){ // oops. Not excluded. System.err.println( Localizer.getString("javafilter.class_interface_but_not_exclude" ,classname) ); loadClass( classname ); return true; } ClassEntry c = stringToClass( classname ); if ( c == null ){ System.out.println( Localizer.getString("javafilter.cannot_find_class", classname) ); hadError = true; return false; } if ( ! analyzer.makeInterface( c ) ){ // big magic here! System.err.println( Localizer.getString("javafilter.could_not_make_interface", classname)); hadError = true; return false;// something bad happened here. } Enumeration memberlist = c.members(); if ( memberlist == null ) return true; // no work. while ( memberlist.hasMoreElements() ){ DependenceNode node = (DependenceNode)( memberlist.nextElement() ); node.flags |= INTF; } return true; } /* * -wholeClass classname * -wholeClass packagename.* * also implicit in -loadClass */ private void allClassMembers( String name ){ if ( wholeClass == null ) wholeClass = new ClassnameFilter(); wholeClass.includeName( sanitizeClassname( name ) ); } /* * -wholeData classname * -wholeData packagename.* */ private void allClassData( String name ){ if ( wholeData == null ) wholeData = new ClassnameFilter(); wholeData.includeName( sanitizeClassname( name ) ); } class DependenceRelation{ String fromName; String verb; String toName; DependenceRelation( String f, String v, String t ){ fromName = f; verb = v; toName = t; } } /* * addedDepencence -- called in response to a command-line * option such as * -dependence full-member-name verb full-member-name */ private void addDependence( DependenceRelation r ){ DependenceNode fromNode = stringToNode( r.fromName ); MemberDependenceNode destMember = null; DependenceNode destClass = null; boolean toClass = false; int arcType; if ( r.verb.equalsIgnoreCase("reads") ){ toClass = false; arcType = ARC_READS; } else if ( r.verb.equalsIgnoreCase("writes") ){ toClass = false; arcType = ARC_WRITES; } else if ( r.verb.equalsIgnoreCase("calls") ){ toClass = false; arcType = ARC_CALLS; } else if ( r.verb.equalsIgnoreCase("classref") ){ toClass = true; arcType = ARC_CLASS; } else { System.out.println( Localizer.getString("javafilter.unrecognized_dependence_verb", r.verb)); hadError = true; return; } try { if ( toClass ){ destClass = stringToClass( r.toName ); } else { destMember = stringToNode( r.toName ); destClass = ((MemberName)(destMember.name())).classEntry; analyzer.addDependenceArc( fromNode, destMember, arcType ); } analyzer.addDependenceArc( fromNode, destClass, ARC_CLASS ); } catch ( NullPointerException e ){ /* * This is extra-cautious. I don't believe any of these * situations can occur using the current DependenceAnalyzers. * I believe that, barring catastrophies such as running out of memory, * data structures will be created on-the-fly by the stringToXXX * methods. */ if ( fromNode == null ){ System.out.println( Localizer.getString("javafilter.cannot_find", r.fromName)); hadError = true; return; } if ( !toClass ){ if ( destMember == null ){ System.out.println( Localizer.getString("javafilter.cannot_find", r.toName)); hadError = true; return; } } if ( destClass == null ){ System.out.println( Localizer.getString("javafilter.cannot_find", r.toName)); hadError = true; return; } // else, none of the above // propigate the error, as it is not one we want to deal with. throw e; } } /* * doLoad(). Called at the end of argument processing. */ private void doLoad(){ // first, work off the lists we collected // during argument processing. // all the -classpath and -exclude stuff // has been taken care of. // add interfaces and dependences first. // then things to load Enumeration work = interfaceClasses.elements(); while ( work.hasMoreElements() ){ interfaceClass( (String) ( work.nextElement() ) ); } work = dependences.elements(); while ( work.hasMoreElements() ){ addDependence( (DependenceRelation)( work.nextElement() ) ); } work = membersToLoad.elements(); while ( work.hasMoreElements() ){ loadMember( (String)( work.nextElement() ) ); } work = constructorsToLoad.elements(); while ( work.hasMoreElements() ){ loadConstructor( (String)( work.nextElement() ) ); } work = classesToLoad.elements(); while ( work.hasMoreElements() ){ loadClass( (String)( work.nextElement() ) ); } if ( hadError ) return; // oops. Vector loaded; if ( initialList.size() == 0) return; // vacuous! if ( verbosity > 0 ){ System.err.println( Localizer.getString("javafilter.loading_based_on_these_requirements")); printList( System.err, initialList ); } if ( wholeClass == null ) wholeClass = new NeverAccept(); if ( wholeData == null ) wholeData = new NeverAccept(); loaded = processList( initialList, REQ, INITREQ, REQ|INTF, verbosity ); if ( loaded == null ) { System.err.println(Localizer.getString("javafilter.nothing_to_load")); } else { System.out.println("## "+loaded.size()+" things required"); printList( System.out, loaded ); if ( nativelist != null ){ printFlaggedList( nativelist, loaded, MemberDependenceNode.NATIVE ); } } checkList( initialList, System.err ); listErrorNodes(REQ|INITREQ, System.err ); } private void checkList( Vector list, java.io.PrintStream o){ Enumeration namedNodes = list.elements(); boolean headerPrinted = false; while ( namedNodes.hasMoreElements() ){ DependenceNode thing = (DependenceNode)(namedNodes.nextElement() ); if ( thing.state() != DependenceNode.ANALYZED ){ if ( ! headerPrinted ){ o.println(Localizer.getString("javafilter.the_following_not_found")); headerPrinted = true; hadError = true; } o.write('\t'); o.println( thing.name() ); } } } private void listErrorNodes( int reqflag, java.io.PrintStream o ){ Enumeration nodes = analyzer.allNodes(); boolean headerPrinted = false; while ( nodes.hasMoreElements() ){ DependenceNode thing = (DependenceNode)(nodes.nextElement() ); if ( thing.state() == DependenceNode.ERROR ){ // an error node. Not actually interesting // unless we believe it is referenced by something // we care about, and thus would be required // if not erroneous. boolean namePrinted = false; Enumeration dependences = thing.dependedOn(); while ( dependences.hasMoreElements() ){ DependenceArc a = (DependenceArc)(dependences.nextElement() ); DependenceNode fromNode = a.from(); if ( (fromNode.flags&reqflag) != 0 ){ // yes. This is interesting. if ( ! namePrinted ){ if ( ! headerPrinted ) { o.println(Localizer.getString("javafilter.dependences_unsatisfied")); headerPrinted = true; } o.println("\t"+ thing.name() ); namePrinted = true; } String deptype; switch ( a.type() ){ case ARC_READS: deptype = "javafilter.read_by";break; case ARC_WRITES: deptype="javafilter.written_by";break; case ARC_CALLS: deptype="javafilter.called_by";break; case ARC_EXCEPTION: deptype="javafilter.thrown_by";break; case ARC_INTERFACE: deptype="javafilter.implemented_by"; break; default: deptype="javafilter.referenced_by"; break; } o.println( "\t "+Localizer.getString(deptype, fromNode.name())); } } } } } private void printList( java.io.PrintStream o, Vector elementList ){ if ( elementList == null ) { o.println(" <nothing>"); return; } Enumeration loadlist = elementList.elements(); while ( loadlist.hasMoreElements() ){ DependenceNode thing = (DependenceNode)(loadlist.nextElement()); o.print(" "); o.println( thing.name() ); } } private void printFlaggedList( java.io.PrintStream o, Vector elementList, int flagmask ){ if ( elementList == null ) { o.println(" <nothing>"); return; } Enumeration loadlist = elementList.elements(); while ( loadlist.hasMoreElements() ){ DependenceNode thing = (DependenceNode)(loadlist.nextElement()); if ( (thing.flags&flagmask) != 0 ){ o.println( thing.name() ); } } o.flush(); } private void processArgs( String args[] ){ for ( int i = 0; i < args.length; i++ ){ String a = args[i]; /* * -classpath is common to all uses of * dependence analysis. The argument is a * :-separated list of places to look for * class files. This can include jar/zip files * and directories. This can be given multiple * times, and is cumulative. */ if ( a.equals("-classpath") ){ finder.addToSearchPath( args[++i] ); continue; } /* * -exclude is common to all uses of dependence * analysis. The argument is a full class name, * for a class which is not to be analysed * (except for the names it exports and * superclass relationships). If the last * component of the name is '*', then this * represents a whole package to be excluded. */ if ( a.equals("-exclude") ){ excludeClass( args[++i] ); continue; } /* * -f filename * to name a file containing more commands. * It is used mostly for including canned * sequences of commands, such as those naming * all the classes required by the implementation. * They are processed in order, immediately. */ if ( a.equals("-f" ) ){ String filename = args[++i]; try { String newargs[] = parseOptionFile( filename ); processArgs( newargs ); // just call us recursively. } catch ( java.io.IOException ioe ){ System.out.println( Localizer.getString("javafilter.could_not_process", filename) ); ioe.printStackTrace(); hadError = true; } continue; } /* * -v increases verbosity. */ if ( a.equals("-v" ) ){ verbosity += 1; continue; } /* * -nativelist filename * to have the names of all native methods included * written on the named file. */ if ( a.equals("-nativelist") ){ try { nativelist = new BufferedPrintStream( new FileOutputStream( args[++i] ) ); } catch ( Exception e ){ System.err.println( Localizer.getString("javafilter.cannot_open_nativelist", args[i]) ); hadError = true; } continue; } /* * Dependence loading. * -load full-member-name to specify a member. */ if ( a.equals("-load") ){ membersToLoad.addElement( args[++i] ); continue; } /* * -new full-class-name * -new full-class-name(sig)V * A shorthand for naming constructors * -load full-class-name.<init>()V or * -load full-class-name.<init>(sig)V */ if ( a.equals("-new") ){ constructorsToLoad.addElement( args[++i] ); continue; } /* * -main full-class-name is shorthand for * -load full-class-name.main(Ljava/lang/String;)V */ if ( a.equals("-main") ){ membersToLoad.addElement( args[++i] + "." + mainName + mainSig ); continue; } /* * -loadClass full-class-name to specify all * members of a class. */ if ( a.equals("-loadClass") ){ classesToLoad.addElement( args[++i] ); continue; } /* * -interface full-class-name to specify a * contract that must be honored by * subclasses of the named class or * implementors of an interface. */ if ( a.equals("-interface") ){ interfaceClasses.addElement( args[++i] ); continue; } /* * -wholeClass full-class-name or * -wholeClass packagename.* * load the whole class. */ if ( a.equals("-wholeClass") ){ allClassMembers( args[++i] ); continue; } /* * -wholeData full-class-name or * -wholeData packagename.* * load all the data. */ if ( a.equals("-wholeData") ){ allClassData( args[++i] ); continue; } /* * -dependence full-member-name verb full-member-name * To describe to the program dependences that it cannot * be understood. Chiefly to describe * dependences from native-code methods, which cannot * otherwise be analysed. "verb" is one of: * reads * writes * calls * classRef, in which case the 3rd operand is actually * a full-class-name */ if ( a.equals("-dependence") ){ dependences.addElement( new DependenceRelation( args[++i], args[++i], args[++i] ) ); continue; } System.out.println(Localizer.getString("javafilter.command_not_recognized", args[i])); hadError = true; } } private Vector interfaceClasses; private Vector dependences; private Vector membersToLoad; private Vector constructorsToLoad; private Vector classesToLoad; private boolean process( String args[] ){ interfaceClasses = new Vector(); dependences = new Vector(); membersToLoad = new Vector(); constructorsToLoad = new Vector(); classesToLoad = new Vector(); processArgs( args ); if ( ! hadError ) doLoad(); return hadError; } public static void main( String args[] ){ boolean fail = (new JavaFilter().process( args )); if ( fail ){ System.exit( 1 ); } } class NeverAccept extends util.ClassnameFilter{ public boolean accept( java.io.File dir, String className ){ return false; } } }