Get things working... even the static analysis, with real testcases and everything ;-)
authorBenjamin Smedberg <benjamin@smedbergs.us>
Thu, 12 Jun 2008 10:34:05 -0400
changeset 128 d76de26799c6cd5bfde2e1a4b337a63387efceb1
parent 127 9198aa54906777d1a77e806921e89bd9464ac88e
child 129 09bc16d43f7bf6f49ed07aa09802a6c8e2b41b03
push id14
push userbsmedberg@mozilla.com
push dateThu, 12 Jun 2008 14:34:20 +0000
Get things working... even the static analysis, with real testcases and everything ;-)
mozStorageTransaction-is-stackclass
mozStorageTransaction-is-stackclass2
series
static-check-gc-attributes
--- a/mozStorageTransaction-is-stackclass
+++ b/mozStorageTransaction-is-stackclass
@@ -1,16 +1,32 @@
 mozStorageTransaction should only appear on the stack. Ready for upstream to mozilla-central
 
 diff --git a/storage/public/mozStorageHelper.h b/storage/public/mozStorageHelper.h
 --- a/storage/public/mozStorageHelper.h
 +++ b/storage/public/mozStorageHelper.h
-@@ -57,7 +57,9 @@
+@@ -57,7 +57,8 @@
   * you may not get the transaction type you ask for, and you won't be able
   * to rollback.
   */
 -class mozStorageTransaction
 +class
-+  NS_STACK_CLASS
 +mozStorageTransaction
  {
  public:
    mozStorageTransaction(mozIStorageConnection* aConnection,
+@@ -136,7 +137,7 @@ public:
+   }
+ 
+ protected:
+-  nsCOMPtr<mozIStorageConnection> mConnection;
++  mozIStorageConnection* mConnection;
+   PRBool mHasTransaction;
+   PRBool mCommitOnComplete;
+   PRBool mCompleted;
+@@ -151,6 +152,7 @@ protected:
+  * need resetting, the reset operation is inexpensive.
+  */
+ class mozStorageStatementScoper
++  NS_STACK_CLASS
+ {
+ public:
+   mozStorageStatementScoper(mozIStorageStatement* aStatement)
new file mode 100644
--- /dev/null
+++ b/mozStorageTransaction-is-stackclass2
@@ -0,0 +1,14 @@
+diff --git a/storage/public/mozStorageHelper.h b/storage/public/mozStorageHelper.h
+--- a/storage/public/mozStorageHelper.h
++++ b/storage/public/mozStorageHelper.h
+@@ -151,8 +151,9 @@ protected:
+  * Note that this always just resets the statement. If the statement doesn't
+  * need resetting, the reset operation is inexpensive.
+  */
+-class mozStorageStatementScoper
++class
+   NS_STACK_CLASS
++mozStorageStatementScoper
+ {
+ public:
+   mozStorageStatementScoper(mozIStorageStatement* aStatement)
--- a/series
+++ b/series
@@ -131,8 +131,9 @@ nativewrapper-finalizers
 http-connection-reclamation
 root-stringbundles
 rdf-hashtable
 mozStorageTransaction-is-stackclass
 no-immediate-CXXFLAGS
 root-nsNavHistory
 remove-manual-addrefs
 rdf-anonymous-resource-refcounting
+mozStorageTransaction-is-stackclass2
--- a/static-check-gc-attributes
+++ b/static-check-gc-attributes
@@ -1,12 +1,578 @@
 Add basic static checking of gc types and correct inheritance of GCFinalizable. I found out that it's not actually catching 90% of the stack classes, for reasons I haven't figured out. This patch actually kinda sucks, so I'm going to go back to the beginning and make this entire script pure treehydra.
 * * *
 Treehydra rocks. Dehydra is so-so. Switch the rest of this to use treehydra!
+* * *
 
+diff --git a/config/config.mk b/config/config.mk
+--- a/config/config.mk
++++ b/config/config.mk
+@@ -529,10 +529,12 @@ DEHYDRA_SCRIPT = $(topsrcdir)/xpcom/anal
+ 
+ DEHYDRA_MODULES = \
+   $(topsrcdir)/xpcom/analysis/stack.js \
++  $(topsrcdir)/xpcom/analysis/final.js \
+   $(NULL)
+ 
+ TREEHYDRA_MODULES = \
+   $(topsrcdir)/xpcom/analysis/outparams.js \
++  $(topsrcdir)/xpcom/analysis/gc.js \
+   $(NULL)
+ 
+ DEHYDRA_ARGS = \
+diff --git a/xpcom/analysis/final.js b/xpcom/analysis/final.js
+new file mode 100644
+--- /dev/null
++++ b/xpcom/analysis/final.js
+@@ -0,0 +1,16 @@
++function process_type(c)
++{
++  if ((c.kind == 'class' || c.kind == 'struct') && !c.isIncomplete) {
++    for each (let base in c.bases)
++      if (isFinal(base))
++	error("Class '" + c.name + "' derives from final class '" + base.name + "'.", c.loc);
++  }
++}
++
++function isFinal(c)
++{
++  if (c.isIncomplete)
++    throw Error("Can't get final property for incomplete type.");
++
++  return hasAttribute(c, 'NS_final');
++}
+diff --git a/xpcom/analysis/gc.js b/xpcom/analysis/gc.js
+new file mode 100644
+--- /dev/null
++++ b/xpcom/analysis/gc.js
+@@ -0,0 +1,211 @@
++include("unstable/lazy_types.js");
++include("gcc_util.js");
++
++function process_type(c)
++{
++  if (!c.isIncomplete && (c.kind == 'class' || c.kind == 'struct')) {
++    if (inheritsFromGCFinalizable(c) &&
++	!leftmostFinalizable(c))
++      error("MMgc::GCFinalizable is not the left-most base of class " + c.name, c.loc);
++
++    if (isXPCOMGCFinalizedObject(c) &&
++	!leftmostFinalizable(c) &&
++       c.name != "XPCOMGCFinalizedObject")
++      error("MMgc::GCFinalizable is not the left-most base of class " + c.name + " which inherits from XPCOMGCFinalizedObject", c.loc);
++  }
++}
++
++function isGC(c)
++{
++  function calculate()
++  {
++    if (hasAttribute(c, 'NS_GC') ||
++	c.name == 'MMgc::GCObject' ||
++	c.name == 'MMgc::GCFinalizable' ||
++	c.name == 'XPCOMGCObject' ||
++	c.name == 'XPCOMGCFinalizedObject')
++      return true;
++
++    // nsIFrame is a figment of your imagination!
++    if (c.name == 'nsIFrame')
++      return false;
++
++    for each (let base in c.bases)
++    if (isGC(base, false))
++      return true;
++
++    for each (let member in c.members) {
++      if (member.isFunction)
++	continue;
++
++      let type = member.type;
++      while (true) {
++	if (type.isArray) {
++	  type = type.type;
++	  continue;
++	}
++
++	if (type.typedef) {
++	  if (hasAttribute(type, 'NS_GC'))
++	    return true;
++
++	  type = type.typedef;
++	  continue;
++	}
++	break;
++      }
++
++      if (type === undefined) {
++	warning("incomplete type for member " + member + ".", member.loc);
++	continue;
++      }
++
++      if (type.isPointer || type.isReference)
++	continue;
++
++      if (!type.kind || (type.kind != 'class' && type.kind != 'struct'))
++	continue;
++
++      if (isGC(type, false))
++	return true;
++    }
++    return false;
++  }
++
++  if (c.isIncomplete)
++    throw Error("Can't get GC status of incomplete class.");
++
++  if (!c.hasOwnProperty("isGC"))
++    c.isGC = calculate();
++
++  return c.isGC;
++}
++
++/**
++ * is the left-most base of this class (exluding XPCOMGCFinalizedObject)
++ * MMgc::GCFinalizable?
++ */ 
++function leftmostFinalizable(c)
++{
++  function calculate()
++  {
++    if (c.name == 'MMgc::GCFinalizable')
++      return true;
++
++    if (!c.bases)
++      return false;
++
++    if (c.bases.length > 0 &&
++	leftmostFinalizable(c.bases[0]))
++      return true;
++
++    if (c.bases.length > 1 &&
++	c.bases[0].name == 'XPCOMGCFinalizedObject' &&
++	leftmostFinalizable(c.bases[1]))
++      return true;
++
++    return false;
++  }
++
++  if (!c.hasOwnProperty('leftmostFinalizable'))
++    c.leftmostFinalizable = calculate();
++
++  return c.leftmostFinalizable;
++}
++
++/**
++ * is MMGc::GCFinalizable anywhere in this class lineage?
++ */ 
++function inheritsFromGCFinalizable(c)
++{
++  function calculate()
++  {
++    if (c.name == 'MMgc::GCFinalizable')
++      return true;
++
++    for each (let base in c.bases) {
++      if (inheritsFromGCFinalizable(base))
++	return true;
++    }
++
++    return false;
++  }
++
++  if (!c.hasOwnProperty('inheritsFromGCFinalizable'))
++    c.inheritsFromGCFinalizable = calculate();
++
++  return c.inheritsFromGCFinalizable;
++}
++
++/**
++ * is XPCOMGCFinalizedObject anywhere in this class lineage?
++ */
++function isXPCOMGCFinalizedObject(c)
++{
++  function calculate()
++  {
++    if (c.name == 'XPCOMGCFinalizedObject')
++      return true;
++
++    if (!c.bases)
++      return false;
++
++    for each (let base in c.bases)
++      if (isXPCOMGCFinalizedObject(base))
++	return true;
++
++    return false;
++  }
++
++  if (!c.hasOwnProperty('isXPCOMGCFinalizedObject'))
++    c.isXPCOMGCFinalizedObject = calculate();
++
++  return c.isXPCOMGCFinalizedObject;
++}
++
++function isBaseOf(child, parent)
++{
++  if (!child.bases)
++    return false;
++
++  for each (let base in child.bases)
++    if (base === parent)
++      return true;
++
++  return false;
++}
++
++function process_function(f, stmts)
++{
++  var stmt;
++  function getLocation()
++  {
++    if (stmt.loc)
++      return stmt.loc;
++    return f.loc;
++  }
++
++  function processVar(v)
++  {
++    // v.fieldOf.type is a struct/class if this is a stack constructor
++    // it's a pointer if this is some other construction technique
++    if (v.isConstructor) {
++      if (!v.isReturn && !v.fieldOf) {
++	print("Calling constructor " + v.name + " without a .fieldOf... information is missing.", getLocation());
++	return;
++      }
++      if ((v.isReturn || !v.fieldOf.type.isPointer) &&
++	  isGC(v.memberOf)) {
++	if (v.fieldOf &&
++	    v.fieldOf.fieldOf &&
++	    v.fieldOf.fieldOf.name == "this") {
++	  return;
++	}
++	error("Constructing GC class " + v.memberOf.name + " on the stack.",
++	  getLocation());
++      }
++    }
++  }
++  for each (stmt in stmts)
++    iter(processVar, stmt.statements);
++}
+diff --git a/xpcom/analysis/stack.js b/xpcom/analysis/stack.js
+--- a/xpcom/analysis/stack.js
++++ b/xpcom/analysis/stack.js
+@@ -1,3 +1,91 @@ function process_function(f, stmts)
++function process_type(c)
++{
++  if ((c.kind == 'class' || c.kind == 'struct') &&
++      !c.isIncomplete)
++    isStack(c);
++}
++
++function isStack(c)
++{
++  function calculate()
++  {
++    if (hasAttribute(c, 'NS_stack'))
++      return true;
++
++    for each (let base in c.bases)
++    if (isStack(base))
++      return true;
++
++    for each (let member in c.members) {
++      if (member.isFunction)
++	continue;
++
++      let type = member.type;
++      while (true) {
++	if (type === undefined)
++	  break;
++
++	if (type.isArray) {
++	  type = type.type;
++	  continue;
++	}
++
++	if (type.typedef) {
++	  if (hasAttribute(type, 'NS_stack'))
++	    return true;
++
++	  type = type.typedef;
++	  continue;
++	}
++	break;
++      }
++
++      if (type === undefined) {
++	warning("incomplete type for member " + member + ".", member.loc);
++	continue;
++      }
++
++      if (type.isPointer || type.isReference)
++	continue;
++
++      if (!type.kind || (type.kind != 'class' && type.kind != 'struct'))
++	continue;
++
++      if (isStack(type))
++	return true;
++    }
++    return false;
++  }
++
++  if (c.isIncomplete)
++    throw Error("Can't get stack property for incomplete type.");
++
++  if (!c.hasOwnProperty('isStack')) {
++    c.isStack = calculate();
++    if (c.isStack)
++      checkForConstructor(c);
++  }
++
++  return c.isStack;
++}
++
++function checkForConstructor(c)
++{
++  // At the moment, any class that is .stack has to have a constructor, or
++  // we can't detect callsites... this may change with treehydra.
++  let foundConstructor = false;
++  for each (let member in c.members) {
++    if (member.isConstructor) {
++      foundConstructor = true;
++      break;
++    }
++  }
++
++  if (!foundConstructor) {
++    warning("class " + c.name + " is marked stack-only but doesn't have a constructor. Static checking can't detect instantiations of this class properly.", c.loc);
++  }
++}
++
+ function process_function(f, stmts)
+ {
+   var stmt;
+@@ -13,10 +101,10 @@ function process_function(f, stmts)
+   {
+     if (v.isConstructor &&
+         v.fieldOf &&
+-        get_class(v.memberOf, false).stack &&
++        isStack(v.memberOf) &&
+         v.fieldOf.type.isPointer) {
+-      error(getLocation() + ": constructed object of type '" +
+-            v.memberOf.name + "' not on the stack.");
++      error("constructed object of type '" +
++            v.memberOf.name + "' not on the stack.", getLocation());
+     }
+   }
+ 
+diff --git a/xpcom/analysis/static-checking.js b/xpcom/analysis/static-checking.js
+--- a/xpcom/analysis/static-checking.js
++++ b/xpcom/analysis/static-checking.js
+@@ -36,192 +36,23 @@ if (treehydra_enabled())
+ if (treehydra_enabled())
+   LoadModules(options['treehydra-modules']);
+ 
+-/**
+- * 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);
+-
+   for each (let module in modules)
+     if (module.hasOwnProperty('process_type'))
+       module.process_type(c);
+ }
+ 
+-/**
+- * 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)
++function hasAttribute(c, attrname)
+ {
+-  var classattr, base, member, type, realtype, foundConstructor;
+-  var bases = [];
++  if (c.attributes === undefined)
++    return false;
+ 
+-  if (c.isIncomplete) {
+-    if (allowIncomplete)
+-      return null;
++  for each (let attr in c.attributes)
++    if (attr.name == 'user' && attr.value[0] == attrname)
++      return true;
+ 
+-    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;
++  return false;
+ }
+ 
+ function process_function(f, stmts)
 diff --git a/xpcom/base/nscore.h b/xpcom/base/nscore.h
 --- a/xpcom/base/nscore.h
 +++ b/xpcom/base/nscore.h
 @@ -487,17 +487,35 @@ typedef PRUint32 nsrefcnt;
   *
   * NS_STACK_CLASS: a class which must only be instantiated on the stack
   * NS_FINAL_CLASS: a class which may not be subclassed
 + * NS_GC_TYPE    : a class which must be GC-allocated
@@ -36,16 +602,108 @@ diff --git a/xpcom/base/nscore.h b/xpcom
  #define NS_NOGC_TYPE
 +#define NS_MAYBEGC
 +#define NS_MAYBEGCFINALIZED
 +#define NS_FINALIZER
 +#define NS_NO_FINALIZER
  #endif
  
  /**
+diff --git a/xpcom/tests/static-checker/GCInheritance1.cpp b/xpcom/tests/static-checker/GCInheritance1.cpp
+new file mode 100644
+--- /dev/null
++++ b/xpcom/tests/static-checker/GCInheritance1.cpp
+@@ -0,0 +1,11 @@
++#include "nsISupports.h"
++
++struct A
++{
++  int i;
++};
++
++// BAD: inherits from GCFinalizable but it's not the left-most base
++struct B : A, nsISupports
++{
++};
+diff --git a/xpcom/tests/static-checker/GCInheritance2.cpp b/xpcom/tests/static-checker/GCInheritance2.cpp
+new file mode 100644
+--- /dev/null
++++ b/xpcom/tests/static-checker/GCInheritance2.cpp
+@@ -0,0 +1,7 @@
++#include "nsISupportsImpl.h"
++
++// BAD: inherits from XPCOMGCFinalizedObject but GCFinalizable is not the
++// left-most base
++struct A : public XPCOMGCFinalizedObject
++{
++};
+diff --git a/xpcom/tests/static-checker/GCNew.cpp b/xpcom/tests/static-checker/GCNew.cpp
+new file mode 100644
+--- /dev/null
++++ b/xpcom/tests/static-checker/GCNew.cpp
+@@ -0,0 +1,12 @@
++#include "nscore.h"
++
++struct NS_GC_TYPE A
++{
++  A();
++  int i;
++};
++
++A* f()
++{
++  return new A();
++}
+diff --git a/xpcom/tests/static-checker/GCNotStack-constructor.cpp b/xpcom/tests/static-checker/GCNotStack-constructor.cpp
+new file mode 100644
+--- /dev/null
++++ b/xpcom/tests/static-checker/GCNotStack-constructor.cpp
+@@ -0,0 +1,17 @@
++#include "nscore.h"
++
++struct NS_GC_TYPE A
++{
++  A();
++  int i;
++};
++
++struct B : A
++{
++  B();
++};
++
++B::B()
++  : A()
++{
++}
+diff --git a/xpcom/tests/static-checker/GCNotStack-constructor2.cpp b/xpcom/tests/static-checker/GCNotStack-constructor2.cpp
+new file mode 100644
+--- /dev/null
++++ b/xpcom/tests/static-checker/GCNotStack-constructor2.cpp
+@@ -0,0 +1,20 @@
++#include "nscore.h"
++
++struct NS_GC_TYPE A
++{
++  A();
++  int i;
++};
++
++struct B : A
++{
++  B();
++};
++
++B::B()
++  : A()
++{
++  // This is a perverted case: you should be allowed to call A::A above on
++  // "this", but not separately!
++  A a;
++}
 diff --git a/xpcom/tests/static-checker/GCNotStack.cpp b/xpcom/tests/static-checker/GCNotStack.cpp
 new file mode 100644
 --- /dev/null
 +++ b/xpcom/tests/static-checker/GCNotStack.cpp
 @@ -0,0 +1,16 @@
 +#include "nscore.h"
 +
 +namespace GCNotStack {
@@ -60,41 +718,93 @@ new file mode 100644
 +{
 +  A a;
 +}
 +
 +} // namespace
 diff --git a/xpcom/tests/static-checker/Makefile.in b/xpcom/tests/static-checker/Makefile.in
 --- a/xpcom/tests/static-checker/Makefile.in
 +++ b/xpcom/tests/static-checker/Makefile.in
-@@ -47,6 +47,15 @@ GARBAGE_DIRS            += $(MDDEPDIR)
+@@ -41,11 +41,32 @@ srcdir = @srcdir@
+ srcdir = @srcdir@
+ VPATH = @srcdir@
+ 
++MODULE = xpcom
++
+ # MAKE_DIRS: List of directories to build while looping over directories.
+ MAKE_DIRS               += $(MDDEPDIR)
+ GARBAGE_DIRS            += $(MDDEPDIR)
  
  include $(DEPTH)/config/autoconf.mk
- 
++
 +GC_FAILURE_TESTCASES = \
 +  GCNotStack.cpp \
++  GCNotStack-constructor2.cpp \
++  GCNotStack-return.cpp \
++  GCInheritance1.cpp \
++  GCInheritance2.cpp \
 +  $(NULL)
 +
-+FINALIZER_FAILURE_TESTCASES = \
-+  TestFinalizer1.cpp \
-+  TestFinalizer2.cpp \
++GC_PASS_TESTCASES = \
++  GCNew.cpp \
++  GCNotStack-constructor.cpp \
++  GCNotStack-static.cpp \
 +  $(NULL)
 +
++#FINALIZER_FAILURE_TESTCASES = \
++#  TestFinalizer1.cpp \
++#  TestFinalizer2.cpp \
++#  $(NULL)
+ 
  FINAL_FAILURE_TESTCASES = \
    TestFinal.cpp \
-   TestFinalTemplate.cpp \
-@@ -90,6 +99,8 @@ OUTPARAMS_PASS_TESTCASES = \
+@@ -55,6 +76,11 @@ STACK_FAILURE_TESTCASES = \
+ STACK_FAILURE_TESTCASES = \
+   TestStack.cpp \
+   TestStackTemplate.cpp \
++  TestStackCOMPtr.cpp \
++  $(NULL)
++
++STACK_WARNING_TESTCASES = \
++  StackConstructorWarning.cpp \
+   $(NULL)
+ 
+ OUTPARAMS_WARNING_TESTCASES = \
+@@ -90,16 +116,20 @@ OUTPARAMS_PASS_TESTCASES = \
    $(NULL)
  
  STATIC_FAILURE_TESTCASES = \
 +  $(GC_FAILURE_TESTCASES) \
 +  $(FINALIZER_FAILURE_TESTCASES) \
    $(FINAL_FAILURE_TESTCASES) \
    $(STACK_FAILURE_TESTCASES) \
    $(NULL)
+ 
+ STATIC_WARNING_TESTCASES = \
+   $(OUTPARAMS_WARNING_TESTCASES) \
++  $(STACK_WARNING_TESTCASES) \
+   $(NULL)
+ 
+ STATIC_PASS_TESTCASES = \
+   $(OUTPARAMS_PASS_TESTCASES) \
++  $(GC_PASS_TESTCASES) \
+   $(NULL)
+ 
+ include $(topsrcdir)/config/rules.mk
+diff --git a/xpcom/tests/static-checker/StackConstructorWarning.cpp b/xpcom/tests/static-checker/StackConstructorWarning.cpp
+new file mode 100644
+--- /dev/null
++++ b/xpcom/tests/static-checker/StackConstructorWarning.cpp
+@@ -0,0 +1,6 @@
++#include "nscore.h"
++
++struct NS_STACK_CLASS A
++{
++  int i;
++};
 diff --git a/xpcom/tests/static-checker/TestFinalizer1.cpp b/xpcom/tests/static-checker/TestFinalizer1.cpp
 new file mode 100644
 --- /dev/null
 +++ b/xpcom/tests/static-checker/TestFinalizer1.cpp
 @@ -0,0 +1,14 @@
 +#include "nsISupports.h"
 +#include "nsIArray.h"
 +#include "nsCOMPtr.h"
@@ -125,8 +835,19 @@ new file mode 100644
 +
 +A::~A()
 +{
 +  // The following call is statically incorrect (passing a GC object in
 +  // a finalizer)
 +  nsCOMArray<nsIArray> a;
 +  a.AppendObject(nsnull);
 +}
+diff --git a/xpcom/tests/static-checker/TestStackCOMPtr.cpp b/xpcom/tests/static-checker/TestStackCOMPtr.cpp
+new file mode 100644
+--- /dev/null
++++ b/xpcom/tests/static-checker/TestStackCOMPtr.cpp
+@@ -0,0 +1,6 @@
++#include "nsCOMPtr.h"
++
++void TestFunc()
++{
++  nsCOMPtr<nsISupports> f = nsnull;
++}