зеркало из https://github.com/mozilla/pjs.git
216 строки
4.5 KiB
JavaScript
216 строки
4.5 KiB
JavaScript
/* -*- Mode: Java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
|
/**
|
|
* A script for GCC-dehydra to analyze the Mozilla codebase and catch
|
|
* patterns that are incorrect, but which cannot be detected by a compiler. */
|
|
|
|
/**
|
|
* gClassMap maps class names to an object with the following properties:
|
|
*
|
|
* .final = true if the class has been annotated as final, and may not be
|
|
* subclassed
|
|
* .stack = true if the class has been annotated as a class which may only
|
|
* be instantiated on the stack
|
|
*/
|
|
var gClassMap = {};
|
|
|
|
function ClassType(name)
|
|
{
|
|
this.name = name;
|
|
}
|
|
|
|
ClassType.prototype = {
|
|
final: false,
|
|
stack: false,
|
|
};
|
|
|
|
function process_type(c)
|
|
{
|
|
if (c.kind == 'class' || c.kind == 'struct')
|
|
get_class(c, true);
|
|
}
|
|
|
|
/**
|
|
* Get the ClassType for a type 'c'
|
|
*
|
|
* If allowIncomplete is true and the type is incomplete, this function
|
|
* will return null.
|
|
*
|
|
* If allowIncomplete is false and the type is incomplete, this function will
|
|
* throw.
|
|
*/
|
|
function get_class(c, allowIncomplete)
|
|
{
|
|
var classattr, base, member, type, realtype, foundConstructor;
|
|
var bases = [];
|
|
|
|
if (c.isIncomplete) {
|
|
if (allowIncomplete)
|
|
return null;
|
|
|
|
throw Error("Can't process incomplete type '" + c + "'.");
|
|
}
|
|
|
|
if (gClassMap.hasOwnProperty(c.name)) {
|
|
return gClassMap[c.name];
|
|
}
|
|
|
|
for each (base in c.bases) {
|
|
realtype = get_class(base, allowIncomplete);
|
|
if (realtype == null) {
|
|
error("Complete type " + c + " has incomplete base " + base);
|
|
return null;
|
|
}
|
|
|
|
bases.push(realtype);
|
|
}
|
|
|
|
function hasAttribute(attrname)
|
|
{
|
|
var attr;
|
|
|
|
if (c.attributes === undefined)
|
|
return false;
|
|
|
|
for each (attr in c.attributes) {
|
|
if (attr.name == 'user' && attr.value[0] == attrname) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
classattr = new ClassType(c.name);
|
|
gClassMap[c.name] = classattr;
|
|
|
|
// check for .final
|
|
|
|
if (hasAttribute('NS_final')) {
|
|
classattr.final = true;
|
|
}
|
|
|
|
// check for .stack
|
|
|
|
if (hasAttribute('NS_stack')) {
|
|
classattr.stack = true;
|
|
}
|
|
else {
|
|
for each (base in bases) {
|
|
if (base.stack) {
|
|
classattr.stack = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!classattr.stack) {
|
|
// Check members
|
|
for each (member in c.members) {
|
|
if (member.isFunction)
|
|
continue;
|
|
|
|
type = member.type;
|
|
|
|
/* recurse through arrays and typedefs */
|
|
while (true) {
|
|
if (type === undefined) {
|
|
break;
|
|
}
|
|
|
|
if (type.isArray) {
|
|
type = type.type;
|
|
continue;
|
|
}
|
|
if (type.typedef) {
|
|
type = type.typedef;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (type === undefined) {
|
|
warning("incomplete type for member " + member + ".");
|
|
continue;
|
|
}
|
|
|
|
if (type.isPointer || type.isReference) {
|
|
continue;
|
|
}
|
|
|
|
if (!type.kind || (type.kind != 'class' && type.kind != 'struct')) {
|
|
continue;
|
|
}
|
|
|
|
var membertype = get_class(type, false);
|
|
if (membertype.stack) {
|
|
classattr.stack = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for errors at declaration-time
|
|
|
|
for each (base in bases) {
|
|
if (base.final) {
|
|
error("class '" + c.name + "' inherits from final class '" + base.name + "'.");
|
|
}
|
|
}
|
|
|
|
// At the moment, any class that is .final has to have a constructor, or
|
|
// we can't detect callsites... this may change with treehydra.
|
|
if (classattr.stack) {
|
|
foundConstructor = false;
|
|
for each (member in c.members) {
|
|
if (member.isConstructor) {
|
|
foundConstructor = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!foundConstructor) {
|
|
warning(c.loc + ": class " + c.name + " is marked stack-only but doesn't have a constructor. Static checking can't detect instantiations of this class properly.");
|
|
}
|
|
}
|
|
|
|
return classattr;
|
|
}
|
|
|
|
/**
|
|
* Unwrap any array of types back to their base type.
|
|
*/
|
|
function unwrapArray(t)
|
|
{
|
|
while (t.isArray) {
|
|
t = t.type;
|
|
}
|
|
return t;
|
|
}
|
|
|
|
function process_function(f, stmts)
|
|
{
|
|
var stmt;
|
|
function getLocation()
|
|
{
|
|
if (stmt.loc)
|
|
return stmt.loc;
|
|
|
|
return f.loc;
|
|
}
|
|
|
|
function processVar(v)
|
|
{
|
|
if (v.isConstructor &&
|
|
v.fieldOf &&
|
|
get_class(v.methodOf, false).stack &&
|
|
v.fieldOf.type.isPointer) {
|
|
error(getLocation() + ": constructed object of type '" +
|
|
v.methodOf.name + "' not on the stack.");
|
|
}
|
|
}
|
|
|
|
for each (stmt in stmts) {
|
|
iter(processVar, stmt.statements);
|
|
}
|
|
}
|