Index: build.xml =================================================================== --- build.xml (revision 1358274) +++ build.xml (working copy) @@ -58,6 +58,15 @@ + + + + + + + + + Index: lucene/build.xml =================================================================== --- lucene/build.xml (revision 1358274) +++ lucene/build.xml (working copy) @@ -174,6 +174,14 @@ + + + + + + + + Index: lucene/tools/build.xml =================================================================== --- lucene/tools/build.xml (revision 1358274) +++ lucene/tools/build.xml (working copy) @@ -24,7 +24,11 @@ - + + + + + - + + + Index: lucene/tools/custom-tasks.xml =================================================================== --- lucene/tools/custom-tasks.xml (revision 1358274) +++ lucene/tools/custom-tasks.xml (working copy) @@ -55,4 +55,16 @@ + + + + + + + + + + + + Index: lucene/tools/forbiddenMethods/commons-io.txt =================================================================== --- lucene/tools/forbiddenMethods/commons-io.txt (revision 0) +++ lucene/tools/forbiddenMethods/commons-io.txt (working copy) @@ -0,0 +1,20 @@ +# These methods from commons-io should not be used by Solr classes (unsafe, no charset,...): + +org.apache.commons.io.IOUtils#copy(java.io.InputStream,java.io.Writer) +org.apache.commons.io.IOUtils#copy(java.io.Reader,java.io.OutputStream) +org.apache.commons.io.IOUtils#readLines(java.io.InputStream) +org.apache.commons.io.IOUtils#toByteArray(java.io.Reader) +org.apache.commons.io.IOUtils#toByteArray(java.lang.String) +org.apache.commons.io.IOUtils#toCharArray(java.io.InputStream) +org.apache.commons.io.IOUtils#toInputStream(java.lang.CharSequence) +org.apache.commons.io.IOUtils#toInputStream(java.lang.String) +org.apache.commons.io.IOUtils#toString(byte[]) +org.apache.commons.io.IOUtils#toString(java.io.InputStream) +org.apache.commons.io.IOUtils#toString(java.net.URI) +org.apache.commons.io.IOUtils#toString(java.net.URL) +org.apache.commons.io.IOUtils#write(byte[],java.io.Writer) +org.apache.commons.io.IOUtils#write(char[],java.io.OutputStream) +org.apache.commons.io.IOUtils#write(java.lang.CharSequence,java.io.OutputStream) +org.apache.commons.io.IOUtils#write(java.lang.StringBuffer,java.io.OutputStream) +org.apache.commons.io.IOUtils#write(java.lang.String,java.io.OutputStream) +org.apache.commons.io.IOUtils#writeLines(java.util.Collection,java.lang.String,java.io.OutputStream) Index: lucene/tools/forbiddenMethods/commons-io.txt =================================================================== --- lucene/tools/forbiddenMethods/commons-io.txt (revision 0) +++ lucene/tools/forbiddenMethods/commons-io.txt (working copy) Property changes on: lucene/tools/forbiddenMethods/commons-io.txt ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Date Author Id Revision HeadURL \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: lucene/tools/forbiddenMethods/jdk.txt =================================================================== --- lucene/tools/forbiddenMethods/jdk.txt (revision 0) +++ lucene/tools/forbiddenMethods/jdk.txt (working copy) @@ -0,0 +1,20 @@ +# These methods should not be used by Lucene classes (unsafe, no charset,...): + +java.lang.String#(byte[]) +java.lang.String#(byte[],int) +java.lang.String#(byte[],int,int) +java.lang.String#(byte[],int,int) +java.lang.String#getBytes() +java.lang.String#toLowerCase() +java.lang.String#toUpperCase() + +java.io.InputStreamReader#(java.io.InputStream) +java.io.OutputStreamWriter#(java.io.OutputStream) +java.io.FileReader#(java.io.File) +java.io.FileReader#(java.io.FileDescriptor) +java.io.FileReader#(java.lang.String) +java.io.FileWriter#(java.io.File) +java.io.FileWriter#(java.io.File,boolean) +java.io.FileWriter#(java.io.FileDescriptor) +java.io.FileWriter#(java.lang.String) +java.io.FileWriter#(java.lang.String,boolean) Index: lucene/tools/forbiddenMethods/jdk.txt =================================================================== --- lucene/tools/forbiddenMethods/jdk.txt (revision 0) +++ lucene/tools/forbiddenMethods/jdk.txt (working copy) Property changes on: lucene/tools/forbiddenMethods/jdk.txt ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Date Author Id Revision HeadURL \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: lucene/tools/ivy.xml =================================================================== --- lucene/tools/ivy.xml (revision 1358274) +++ lucene/tools/ivy.xml (working copy) @@ -18,4 +18,8 @@ --> + + + + Index: lucene/tools/lib =================================================================== --- lucene/tools/lib (revision 0) +++ lucene/tools/lib (working copy) Property changes on: lucene/tools/lib ___________________________________________________________________ Added: svn:ignore ## -0,0 +1 ## +*.jar Index: lucene/tools/src/java/lucene-solr.antlib.xml =================================================================== --- lucene/tools/src/java/lucene-solr.antlib.xml (revision 1358274) +++ lucene/tools/src/java/lucene-solr.antlib.xml (working copy) @@ -18,4 +18,7 @@ + Index: lucene/tools/src/java/org/apache/lucene/validation/ForbiddenMethodCallCheckTask.java =================================================================== --- lucene/tools/src/java/org/apache/lucene/validation/ForbiddenMethodCallCheckTask.java (revision 0) +++ lucene/tools/src/java/org/apache/lucene/validation/ForbiddenMethodCallCheckTask.java (working copy) @@ -0,0 +1,270 @@ +package org.apache.lucene.validation; + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.EmptyVisitor; +import org.objectweb.asm.commons.Method; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.types.Resource; +import org.apache.tools.ant.types.ResourceCollection; +import org.apache.tools.ant.types.resources.FileResource; +import org.apache.tools.ant.types.resources.Resources; +import org.apache.tools.ant.types.resources.FileResource; +import org.apache.tools.ant.types.resources.StringResource; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.BufferedReader; +import java.io.Reader; +import java.io.File; +import java.io.StringReader; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * Task to check if a set of class files contains calls to forbidden methods + * from a given classpath and list of methods (either inline or as pointer to files). + */ +public class ForbiddenMethodCallCheckTask extends Task { + + private final Resources classFiles = new Resources(); + private final Resources methodDescriptors = new Resources(); + private Path classpath = null; + + private final Map classCache = new HashMap(); + private final Map forbiddenMethods = new HashMap(); + + /** Adds the method signature to the list of disallowed methods. The Signature is checked against the given ClassLoader. */ + private void addMethod(ClassLoader loader, String signature) throws BuildException { + final int p = signature.indexOf('#'); + if (p < 0) + throw new BuildException("Invalid method/constructor signature: " + signature); + final String clazz = signature.substring(0, p); + // we ignore the return type, its just to match easier (so return type is void): + final Method dummy = Method.getMethod("void " + signature.substring(p+1), true); + // check method signature, if it is really existent (in classpath), but we don't really load the class into JVM: + try { + ClassNode c = classCache.get(clazz); + if (c == null) { + final ClassReader reader; + if (loader != null) { + final InputStream in = loader.getResourceAsStream(clazz.replace('.', '/') + ".class"); + if (in == null) { + throw new BuildException("Loading of class " + clazz + " failed: Not found"); + } + try { + reader = new ClassReader(in); + } finally { + in.close(); + } + } else { + // load from build classpath + reader = new ClassReader(clazz); + } + reader.accept(c = new ClassNode(), ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + classCache.put(clazz, c); + } + // list all methods with this signature: + @SuppressWarnings("unchecked") List methods = (List) c.methods; + boolean found = false; + for (final MethodNode mn : methods) { + if (mn.name.equals(dummy.getName()) && Arrays.equals(Type.getArgumentTypes(mn.desc), dummy.getArgumentTypes())) { + found = true; + forbiddenMethods.put(c.name + '\000' + new Method(mn.name, mn.desc), signature); + // don't break when found, as there may be more covariant overrides! + } + } + if (!found) + throw new BuildException("No method with following signature: " + signature); + } catch (IOException e) { + throw new BuildException("Loading of class " + clazz + " failed.", e); + } + } + + /** Parses a class given as Resource and checks for valid method invocations */ + private int checkClass(final Resource res) throws IOException { + final InputStream stream = res.getInputStream(); + try { + final int[] violations = new int[1]; + new ClassReader(stream).accept(new EmptyVisitor() { + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + return new EmptyVisitor() { + private int lineNo; + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc) { + final String key = owner + '\000' + new Method(name, desc); + final String printout = forbiddenMethods.get(key); + if (printout != null) { + violations[0]++; + log(String.format(Locale.ENGLISH, "Forbidden method invocation: %s\n in %s, source line %d", + printout, res.getName(), lineNo), Project.MSG_ERR); + } + } + + @Override + public void visitLineNumber(int lineNo, Label start) { + this.lineNo = lineNo; + } + }; + } + }, ClassReader.SKIP_FRAMES); + return violations[0]; + } finally { + stream.close(); + } + } + + /** Reads a list of method signatures. Closes the Reader when done (on Exception, too)! */ + private void parseMethodFile(ClassLoader loader, Reader reader) throws IOException { + final BufferedReader r = new BufferedReader(reader); + try { + String line; + while ((line = r.readLine()) != null) { + line = line.trim(); + if (line.length() == 0 || line.startsWith("#")) + continue; + addMethod(loader, line); + } + } finally { + r.close(); + } + } + + @Override + public void execute() throws BuildException { + AntClassLoader loader = null; + try { + if (classpath != null) { + classpath.setProject(getProject()); + loader = getProject().createClassLoader(classpath); + } + classFiles.setProject(getProject()); + + try { + @SuppressWarnings("unchecked") + Iterator iter = (Iterator) methodDescriptors.iterator(); + while (iter.hasNext()) { + final Resource r = iter.next(); + if (!r.isExists()) { + throw new BuildException("Resource does not exist: " + r.getName()); + } + if (r instanceof StringResource) { + parseMethodFile(loader, new StringReader(((StringResource) r).getValue())); + } else { + parseMethodFile(loader, new InputStreamReader(r.getInputStream(), "UTF-8")); + } + } + } catch (IOException ioe) { + throw new BuildException("IO problem while reading files with method signatures.", ioe); + } + + long start = System.currentTimeMillis(); + + int checked = 0; + int errors = 0; + @SuppressWarnings("unchecked") + Iterator iter = (Iterator) classFiles.iterator(); + while (iter.hasNext()) { + final Resource r = iter.next(); + if (!r.isExists()) { + throw new BuildException("Class file does not exist: " + r.getName()); + } + + try { + errors += checkClass(r); + } catch (IOException ioe) { + throw new BuildException("IO problem while reading class file " + r.getName(), ioe); + } + checked++; + } + + log(String.format(Locale.ENGLISH, + "Scanned %d class file(s) for forbidden method invocations (in %.2fs), %d error(s).", + checked, (System.currentTimeMillis() - start) / 1000.0, errors), + errors > 0 ? Project.MSG_ERR : Project.MSG_INFO); + + if (errors > 0) { + throw new BuildException("Check for forbidden method calls failed, see log."); + } + } finally { + if (loader != null) loader.cleanup(); + } + } + + /** Set of class files to check */ + public void add(ResourceCollection rc) { + classFiles.add(rc); + } + + /** A file with method signatures methodFile= attribute */ + public void setMethodFile(File file) { + methodDescriptors.add(new FileResource(getProject(), file)); + } + + /** Set of files with method signatures as nested element */ + public FileSet createMethodFileSet() { + final FileSet fs = new FileSet(); + fs.setProject(getProject()); + methodDescriptors.add(fs); + return fs; + } + + /** Support for method signature list as nested text */ + public void addText(String text) { + methodDescriptors.add(new StringResource(getProject(), text)); + } + + /** Classpath as classpath= attribute */ + public void setClasspath(Path classpath) { + createClasspath().append(classpath); + } + + /** Classpath as classpathRef= attribute */ + public void setClasspathRef(Reference r) { + createClasspath().setRefid(r); + } + + /** Classpath as nested element */ + public Path createClasspath() { + if (this.classpath == null) { + this.classpath = new Path(getProject()); + } + return this.classpath.createPath(); + } + +} Index: lucene/tools/src/java/org/apache/lucene/validation/ForbiddenMethodCallCheckTask.java =================================================================== --- lucene/tools/src/java/org/apache/lucene/validation/ForbiddenMethodCallCheckTask.java (revision 0) +++ lucene/tools/src/java/org/apache/lucene/validation/ForbiddenMethodCallCheckTask.java (working copy) Property changes on: lucene/tools/src/java/org/apache/lucene/validation/ForbiddenMethodCallCheckTask.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Date Author Id Revision HeadURL \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: solr/build.xml =================================================================== --- solr/build.xml (revision 1358274) +++ solr/build.xml (working copy) @@ -187,6 +187,19 @@ + + + + + + + + + + + + +