Skip to content

Instantly share code, notes, and snippets.

@SpaceManiac
Created February 20, 2012 23:04
Show Gist options
  • Save SpaceManiac/1872149 to your computer and use it in GitHub Desktop.
Save SpaceManiac/1872149 to your computer and use it in GitHub Desktop.
A Python/Solum script to check Bukkit plugins against a given Bukkit or CraftBukkit jar.
#!/usr/bin/python
# compatchecker.py
#
# Scans .jar files passed on command line for compatability with a given
# Bukkit or CraftBukkit jar.
#
# Requires Solum (https://github.com/TkTech/Solum)
# sudo pip install git+git://github.com/TkTech/Solum.git
import sys, urllib2, re
from glob import glob
from solum import JarFile, ClassFile, ConstantType
from os import path
# finding things
craftbukkit = False
def find(name):
if craftbukkit:
return name.startswith('org/bukkit/') or name.startswith('net/minecraft/')
else:
return name.startswith('org/bukkit') and not name.startswith('org/bukkit/craftbukkit/')
def findClass(constant):
return find(constant['name']['value'])
def findReference(constant):
return find(constant['class']['name']['value'])
def recurseMethod(jar, bCf, bMethod, signature):
if bCf.methods.find_one(name=bMethod, args=signature[0], returns=signature[1]):
return True
for interface in bCf.interfaces:
interface = interface['name']['value']
if find(interface) and recurseMethod(jar, jar.open_class(interface), bMethod, signature):
return True
return find(bCf.superclass) and recurseMethod(jar, jar.open_class(bCf.superclass), bMethod, signature)
# formatting things
typeMap = {
'V': 'void',
'Z': 'boolean',
'I': 'int',
'D': 'double',
'J': 'long',
'F': 'float',
'S': 'short',
'B': 'byte',
'C': 'char'
}
def formatType(type):
if type in typeMap:
return typeMap[type]
elif type.startswith("["):
return formatType(type[1:]) + '[]'
elif type.startswith("L"):
return type[1:-1].replace('/', '.')
elif type == "":
return ""
else:
print "Unmappable type '%s'" % type
return type
def makeSignature(raw):
i = raw.find(')')
rawArgs = raw[1:i]
rawRet = raw[i+1:]
ret = formatType(rawRet)
args = []
i = 0
isArray = ""
while i < len(rawArgs):
if rawArgs[i] == 'L':
i2 = rawArgs.find(';', i)
args.append(formatType(rawArgs[i:i2+1]) + isArray)
isArray = ""
i = i2 + 1
elif rawArgs[i] == '[':
isArray = "[]"
i += 1
else:
args.append(formatType(rawArgs[i]) + isArray)
isArray = ""
i += 1
return (tuple(args), ret)
def printableArgs(args):
return '(' + ', '.join(args) + ')'
def prettyName(cname):
return cname[:-6].replace("/", ".")
# main
if __name__ == "__main__":
verbose = quiet = False
files = []
cb = ""
for arg in sys.argv[1:]:
if arg == "-v":
verbose = True
quiet = False
elif arg == "-q":
quiet = True
verbose = False
elif cb == "":
cb = arg
else:
files.append(arg)
if len(files) == 1 and files[0].find('*') != -1:
files = glob(files[0])
if len(files) == 0 or cb == "":
print "usage: %s [-vq] <bukkit.jar or craftbukkit.jar> <plugin.jar> [plugin.jar ...]" % sys.argv[0]
print "ex: %s craftbukkit-1.1-R5-SNAPHOST.jar plugins/*.jar" % sys.argv[0]
sys.exit(0)
bukkit = JarFile(cb)
try:
bukkit.open_class('org/bukkit/Bukkit.class')
except KeyError:
print 'First argument must be a valid bukkit.jar or craftbukkit.jar'
sys.exit(0)
try:
bukkit.open_class('org/bukkit/craftbukkit/CraftServer.class')
craftbukkit = True
print 'You have passed a craftbukkit.jar, CB and NMS references will be checked'
except KeyError:
craftbukkit = False
print 'You have passed a bukkit.jar, CB and NMS references will not be checked'
total = numBad = 0
for filename in files:
total += 1
print "Scanning %s..." % filename
jar = JarFile(filename)
jarBadrefs = 0
for cname in jar.class_list:
cf = jar.open_class(cname)
badClasses = []
badRefs = []
# classes
for bClass in cf.constants.find(ConstantType.CLASS, f=findClass):
bClass = bClass["name"]["value"]
try:
bukkit.open_class(bClass)
except KeyError:
badClasses.append(bClass)
badRefs.append("class " + bClass.replace('/', '.'))
# methods
temp = cf.constants.find(ConstantType.METHOD_REF, f=findReference)
temp += cf.constants.find(ConstantType.INTERFACE_METHOD_REF, f=findReference)
for bMethod in temp:
signature = makeSignature(bMethod["name_and_type"]["descriptor"]["value"])
bClass = bMethod["class"]["name"]["value"]
bMethod = bMethod["name_and_type"]["name"]["value"]
if not bClass in badClasses:
if not recurseMethod(bukkit, bukkit.open_class(bClass), bMethod, signature):
badRefs.append("method %s %s%s in %s" %
(signature[1], bMethod, printableArgs(signature[0]), bClass.replace('/', '.')))
# fields
for bField in cf.constants.find(ConstantType.FIELD_REF, f=findReference):
bClass = bField["class"]["name"]["value"]
bField = bField["name_and_type"]["name"]["value"]
if not bClass in badClasses:
bCf = bukkit.open_class(bClass)
try:
bCf.fields[bField]
except KeyError:
badRefs.append("field %s in %s" % (bField, bClass.replace('/', '.')))
jarBadrefs += len(badRefs)
if len(badRefs) > 0:
if verbose:
print " %s has %d bad references:" % (prettyName(cname), len(badRefs))
for r in badRefs:
print " " + r
elif not quiet:
print " %s has %d bad references" % (prettyName(cname), len(badRefs))
if quiet and jarBadrefs > 0:
print " %s has a total of %d bad references" % (path.basename(filename), jarBadrefs)
elif not quiet and jarBadrefs == 0:
print " %s has no bad references!" % path.basename(filename)
if jarBadrefs > 0:
numBad += 1
print "%d of %d plugins had bad references (%d%%)" % (numBad, total, numBad * 100 / total)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment