You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucy.apache.org by nw...@apache.org on 2017/02/23 16:27:44 UTC

[1/6] lucy-clownfish git commit: Fix stack string documentation

Repository: lucy-clownfish
Updated Branches:
  refs/heads/master a8f14c2db -> 4aea9977c


Fix stack string documentation

I forgot that we removed special handling of stack strings in DECREF.


Project: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/commit/5c6d7402
Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/5c6d7402
Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/5c6d7402

Branch: refs/heads/master
Commit: 5c6d7402ec7c53b9ef0a2ae79f21ad677e549323
Parents: 2a44a5b
Author: Nick Wellnhofer <we...@aevum.de>
Authored: Fri Feb 17 13:19:54 2017 +0100
Committer: Nick Wellnhofer <we...@aevum.de>
Committed: Fri Feb 17 13:21:53 2017 +0100

----------------------------------------------------------------------
 runtime/core/Clownfish/String.cfh | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/5c6d7402/runtime/core/Clownfish/String.cfh
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/String.cfh b/runtime/core/Clownfish/String.cfh
index 20841b2..0c5f04e 100644
--- a/runtime/core/Clownfish/String.cfh
+++ b/runtime/core/Clownfish/String.cfh
@@ -157,12 +157,12 @@ public final class Clownfish::String nickname Str
      * in `const char *ptr`.
      *
      * The UTF-8 data is not checked for validity. The memory pointed to
-     * by `ptr` must stay unchanged for the lifetime of the stack string,
-     * especially when passed as `decremented` argument. It's not required
-     * to release stack strings with `DECREF`, making them practical to be
-     * used with string literals:
+     * by `ptr` must stay unchanged for the lifetime of the stack string.
+     * Stack strings must never be released with `DECREF` and they must
+     * not be passed as decremented arguments. They're typically used
+     * with string literals:
      *
-     *     Vec_Push(vec, SSTR_WRAP_C("element"));
+     *     String *s = SSTR_WRAP_C("text");
      *
      * Calling `INCREF` on a stack string returns a heap-allocated copy of
      * the string as a separate object, making it crucial to always store


[6/6] lucy-clownfish git commit: Fix potential memory corruption during Perl global destruction

Posted by nw...@apache.org.
Fix potential memory corruption during Perl global destruction

During global destruction, DESTROY is called in random order on objects
remaining because of refcount leaks or circular references. This can
cause memory corruption with Clownfish objects, so better leak instead
of corrupting memory.

Fixes CLOWNFISH-117.


Project: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/commit/4aea9977
Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/4aea9977
Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/4aea9977

Branch: refs/heads/master
Commit: 4aea9977c3d25a550cd72d95a4e2493da3711a3a
Parents: fbbc859
Author: Nick Wellnhofer <we...@aevum.de>
Authored: Thu Feb 23 16:15:14 2017 +0100
Committer: Nick Wellnhofer <we...@aevum.de>
Committed: Thu Feb 23 16:35:50 2017 +0100

----------------------------------------------------------------------
 .../perl/buildlib/Clownfish/Build/Binding.pm    | 22 ++++++---
 runtime/perl/t/binding/019-obj.t                | 16 ++++++-
 runtime/test/Clownfish/Test/RefObj.c            | 47 ++++++++++++++++++++
 runtime/test/Clownfish/Test/RefObj.cfh          | 34 ++++++++++++++
 4 files changed, 113 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/4aea9977/runtime/perl/buildlib/Clownfish/Build/Binding.pm
----------------------------------------------------------------------
diff --git a/runtime/perl/buildlib/Clownfish/Build/Binding.pm b/runtime/perl/buildlib/Clownfish/Build/Binding.pm
index 61c0abe..4a00766 100644
--- a/runtime/perl/buildlib/Clownfish/Build/Binding.pm
+++ b/runtime/perl/buildlib/Clownfish/Build/Binding.pm
@@ -640,6 +640,8 @@ END_XS_CODE
 }
 
 sub bind_obj {
+    my @hand_rolled = qw( Destroy );
+
     my $pod_spec = Clownfish::CFC::Binding::Perl::Pod->new;
     my $synopsis = <<'END_SYNOPSIS';
     package MyObj;
@@ -737,7 +739,6 @@ END_POD
         pod    => $to_perl_pod,
     );
     $pod_spec->add_method(
-        method => 'Destroy',
         alias  => 'DESTROY',
         pod    => $destroy_pod,
     );
@@ -745,6 +746,20 @@ END_POD
     my $xs_code = <<'END_XS_CODE';
 MODULE = Clownfish     PACKAGE = Clownfish::Obj
 
+void
+DESTROY(self)
+    cfish_Obj *self
+PPCODE:
+    /*
+     * During global destruction, DESTROY is called in random order on
+     * objects remaining because of refcount leaks or circular references.
+     * This can cause memory corruption with Clownfish objects, so better
+     * leak instead of corrupting memory.
+     */
+    if (!PL_dirty) {
+        CFISH_Obj_Destroy(self);
+    }
+
 SV*
 get_class(self)
     cfish_Obj *self
@@ -790,12 +805,9 @@ END_XS_CODE
     my $binding = Clownfish::CFC::Binding::Perl::Class->new(
         class_name => "Clownfish::Obj",
     );
-    $binding->bind_method(
-        alias  => 'DESTROY',
-        method => 'Destroy',
-    );
     $binding->append_xs($xs_code);
     $binding->set_pod_spec($pod_spec);
+    $binding->exclude_method($_) for @hand_rolled;
 
     Clownfish::CFC::Binding::Perl::Class->register($binding);
 }

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/4aea9977/runtime/perl/t/binding/019-obj.t
----------------------------------------------------------------------
diff --git a/runtime/perl/t/binding/019-obj.t b/runtime/perl/t/binding/019-obj.t
index c5eec3b..357984b 100644
--- a/runtime/perl/t/binding/019-obj.t
+++ b/runtime/perl/t/binding/019-obj.t
@@ -16,7 +16,7 @@
 use strict;
 use warnings;
 
-use Test::More tests => 25;
+use Test::More tests => 26;
 use Clownfish::Test;
 
 package TestObj;
@@ -160,3 +160,17 @@ eval { SubclassFinalTestObj->new; };
 like( $@, qr/Can't subclass final class Clownfish::Vector/,
       "Final class can't be subclassed" );
 
+SKIP: {
+    skip( "Circular references leak", 1 )
+        if $ENV{CLOWNFISH_VALGRIND};
+
+    # Create a circular reference on purpose. These objects shouldn't be
+    # destroyed during Perl's global destruction because it could cause
+    # memory corruption.
+    my $ref1 = Clownfish::Test::RefObj->new;
+    my $ref2 = Clownfish::Test::RefObj->new;
+    $ref1->set_ref($ref2);
+    $ref2->set_ref($ref1);
+    pass ( "Created circular reference" );
+}
+

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/4aea9977/runtime/test/Clownfish/Test/RefObj.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/RefObj.c b/runtime/test/Clownfish/Test/RefObj.c
new file mode 100644
index 0000000..07d6825
--- /dev/null
+++ b/runtime/test/Clownfish/Test/RefObj.c
@@ -0,0 +1,47 @@
+/* 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.
+ */
+
+#define C_TESTCFISH_REFOBJ
+#define CFISH_USE_SHORT_NAMES
+#define TESTCFISH_USE_SHORT_NAMES
+
+#include "Clownfish/Test/RefObj.h"
+#include "Clownfish/Class.h"
+#include "Clownfish/Err.h"
+
+RefObj*
+RefObj_new() {
+    return (RefObj*)Class_Make_Obj(REFOBJ);
+}
+
+void
+RefObj_Set_Ref_IMP(RefObj *self, Obj *other) {
+    RefObjIVARS *const ivars = RefObj_IVARS(self);
+    Obj *temp = ivars->ref;
+    ivars->ref = INCREF(other);
+    DECREF(temp);
+}
+
+void
+RefObj_Destroy_IMP(RefObj *self) {
+    RefObjIVARS *const ivars = RefObj_IVARS(self);
+    if (cfish_get_refcount(self) > 1) {
+        THROW(ERR, "Destroy called on referenced object");
+    }
+    DECREF(ivars->ref);
+    SUPER_DESTROY(self, REFOBJ);
+}
+

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/4aea9977/runtime/test/Clownfish/Test/RefObj.cfh
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/RefObj.cfh b/runtime/test/Clownfish/Test/RefObj.cfh
new file mode 100644
index 0000000..4661203
--- /dev/null
+++ b/runtime/test/Clownfish/Test/RefObj.cfh
@@ -0,0 +1,34 @@
+/* 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.
+ */
+
+parcel TestClownfish;
+
+/** Object holding a reference to another object.
+ */
+class Clownfish::Test::RefObj {
+
+    Obj *ref;
+
+    inert incremented RefObj*
+    new();
+
+    void
+    Set_Ref(RefObj *self, Obj *other);
+
+    public void
+    Destroy(RefObj *self);
+}
+


[5/6] lucy-clownfish git commit: Make DESTROY a no-op for Booleans

Posted by nw...@apache.org.
Make DESTROY a no-op for Booleans


Project: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/commit/fbbc8599
Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/fbbc8599
Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/fbbc8599

Branch: refs/heads/master
Commit: fbbc859956006b5b36e08ca18d8f612416ca1c3b
Parents: 25b872f
Author: Nick Wellnhofer <we...@aevum.de>
Authored: Thu Feb 23 15:02:07 2017 +0100
Committer: Nick Wellnhofer <we...@aevum.de>
Committed: Thu Feb 23 15:02:07 2017 +0100

----------------------------------------------------------------------
 runtime/perl/lib/Clownfish.pm | 1 +
 1 file changed, 1 insertion(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/fbbc8599/runtime/perl/lib/Clownfish.pm
----------------------------------------------------------------------
diff --git a/runtime/perl/lib/Clownfish.pm b/runtime/perl/lib/Clownfish.pm
index daca392..8873e4b 100644
--- a/runtime/perl/lib/Clownfish.pm
+++ b/runtime/perl/lib/Clownfish.pm
@@ -159,6 +159,7 @@ sub error {$Clownfish::Err::error}
     our @EXPORT_OK = qw( $true_singleton $false_singleton );
     our $true_singleton  = Clownfish::Boolean->singleton(1);
     our $false_singleton = Clownfish::Boolean->singleton(0);
+    sub DESTROY { }    # leak all
 }
 
 1;


[4/6] lucy-clownfish git commit: Fix refcounts for Perl global error objects

Posted by nw...@apache.org.
Fix refcounts for Perl global error objects

Err_set_error takes a decremented argument. Err_get_error returns a
non-incremented object.


Project: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/commit/25b872f1
Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/25b872f1
Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/25b872f1

Branch: refs/heads/master
Commit: 25b872f1a6019f75d8cc139171b410a1d74ff01b
Parents: 5c6d740
Author: Nick Wellnhofer <we...@aevum.de>
Authored: Thu Feb 23 15:00:54 2017 +0100
Committer: Nick Wellnhofer <we...@aevum.de>
Committed: Thu Feb 23 15:00:54 2017 +0100

----------------------------------------------------------------------
 runtime/perl/xs/XSBind.c | 2 ++
 1 file changed, 2 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/25b872f1/runtime/perl/xs/XSBind.c
----------------------------------------------------------------------
diff --git a/runtime/perl/xs/XSBind.c b/runtime/perl/xs/XSBind.c
index b566f8d..ffc5d20 100644
--- a/runtime/perl/xs/XSBind.c
+++ b/runtime/perl/xs/XSBind.c
@@ -884,6 +884,7 @@ cfish_Err_get_error() {
     PUTBACK;
     FREETMPS;
     LEAVE;
+    CFISH_DECREF(error);
     return error;
 }
 
@@ -906,6 +907,7 @@ cfish_Err_set_error(cfish_Err *error) {
     call_pv("Clownfish::Err::set_error", G_VOID | G_DISCARD);
     FREETMPS;
     LEAVE;
+    CFISH_DECREF(error);
 }
 
 void


[2/6] lucy-clownfish git commit: Fix URI resolution for methods inherited from other parcels

Posted by nw...@apache.org.
Fix URI resolution for methods inherited from other parcels


Project: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/commit/35304580
Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/35304580
Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/35304580

Branch: refs/heads/master
Commit: 35304580521f972ed7d62e432fec95875e7de33f
Parents: a8f14c2
Author: Nick Wellnhofer <we...@aevum.de>
Authored: Mon Feb 13 16:05:28 2017 +0100
Committer: Nick Wellnhofer <we...@aevum.de>
Committed: Fri Feb 17 13:21:53 2017 +0100

----------------------------------------------------------------------
 compiler/src/CFCCHtml.c | 25 +++++++++++++------------
 1 file changed, 13 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/35304580/compiler/src/CFCCHtml.c
----------------------------------------------------------------------
diff --git a/compiler/src/CFCCHtml.c b/compiler/src/CFCCHtml.c
index a70f626..e527c53 100644
--- a/compiler/src/CFCCHtml.c
+++ b/compiler/src/CFCCHtml.c
@@ -147,8 +147,8 @@ static char*
 S_html_create_fresh_methods(CFCClass *klass, CFCClass *ancestor);
 
 static char*
-S_html_create_func(CFCClass *klass, CFCCallable *func, const char *prefix,
-                   const char *short_sym);
+S_html_create_func(CFCClass *klass, CFCClass *doc_class, CFCCallable *func,
+                   const char *prefix, const char *short_sym);
 
 static char*
 S_html_create_param_list(CFCClass *klass, CFCCallable *func);
@@ -668,8 +668,8 @@ S_html_create_functions(CFCClass *klass) {
                              name, "</dt>\n", NULL);
 
         char *short_sym = CFCFunction_short_func_sym(func, klass);
-        char *func_html = S_html_create_func(klass, (CFCCallable*)func, prefix,
-                                             short_sym);
+        char *func_html = S_html_create_func(klass, klass, (CFCCallable*)func,
+                                             prefix, short_sym);
         result = CFCUtil_cat(result, func_html, NULL);
         FREEMEM(func_html);
         FREEMEM(short_sym);
@@ -761,9 +761,10 @@ S_html_create_fresh_methods(CFCClass *klass, CFCClass *ancestor) {
         }
         result = CFCUtil_cat(result, "</dt>\n", NULL);
 
-        char       *short_sym = CFCMethod_short_method_sym(method, klass);
-        char *method_html = S_html_create_func(klass, (CFCCallable*)method,
-                                               prefix, short_sym);
+        char *short_sym = CFCMethod_short_method_sym(method, klass);
+        char *method_html
+            = S_html_create_func(klass, ancestor, (CFCCallable*)method, prefix,
+                                 short_sym);
         result = CFCUtil_cat(result, method_html, NULL);
         FREEMEM(method_html);
         FREEMEM(short_sym);
@@ -777,8 +778,8 @@ S_html_create_fresh_methods(CFCClass *klass, CFCClass *ancestor) {
 }
 
 static char*
-S_html_create_func(CFCClass *klass, CFCCallable *func, const char *prefix,
-                   const char *short_sym) {
+S_html_create_func(CFCClass *klass, CFCClass *doc_class, CFCCallable *func,
+                   const char *prefix, const char *short_sym) {
     CFCType    *ret_type      = CFCCallable_get_return_type(func);
     char       *ret_html      = S_type_to_html(ret_type, "", klass);
     const char *ret_array     = CFCType_get_array(ret_type);
@@ -817,7 +818,7 @@ S_html_create_func(CFCClass *klass, CFCCallable *func, const char *prefix,
     if (docucomment) {
         // Description
         const char *raw_desc = CFCDocuComment_get_description(docucomment);
-        char *desc = S_md_to_html(raw_desc, klass, 0);
+        char *desc = S_md_to_html(raw_desc, doc_class, 0);
         result = CFCUtil_cat(result, desc, NULL);
         FREEMEM(desc);
 
@@ -829,7 +830,7 @@ S_html_create_func(CFCClass *klass, CFCCallable *func, const char *prefix,
         if (param_names[0]) {
             result = CFCUtil_cat(result, "<dl>\n", NULL);
             for (size_t i = 0; param_names[i] != NULL; i++) {
-                char *doc = S_md_to_html(param_docs[i], klass, 0);
+                char *doc = S_md_to_html(param_docs[i], doc_class, 0);
                 result = CFCUtil_cat(result, "<dt>", param_names[i],
                                      "</dt>\n<dd>", doc, "</dd>\n",
                                      NULL);
@@ -842,7 +843,7 @@ S_html_create_func(CFCClass *klass, CFCCallable *func, const char *prefix,
         const char *retval_doc = CFCDocuComment_get_retval(docucomment);
         if (retval_doc && strlen(retval_doc)) {
             char *md = CFCUtil_sprintf("**Returns:** %s", retval_doc);
-            char *html = S_md_to_html(md, klass, 0);
+            char *html = S_md_to_html(md, doc_class, 0);
             result = CFCUtil_cat(result, html, NULL);
             FREEMEM(html);
             FREEMEM(md);


[3/6] lucy-clownfish git commit: Only write documentation for installed parcels

Posted by nw...@apache.org.
Only write documentation for installed parcels


Project: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/commit/2a44a5b3
Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/2a44a5b3
Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/2a44a5b3

Branch: refs/heads/master
Commit: 2a44a5b364facdc17b948beff43f8d9c8ee7e96e
Parents: 3530458
Author: Nick Wellnhofer <we...@aevum.de>
Authored: Mon Feb 13 16:12:19 2017 +0100
Committer: Nick Wellnhofer <we...@aevum.de>
Committed: Fri Feb 17 13:21:53 2017 +0100

----------------------------------------------------------------------
 compiler/src/CFCC.c     | 8 ++++----
 compiler/src/CFCCHtml.c | 9 +++++----
 compiler/src/CFCCMan.c  | 2 --
 compiler/src/CFCClass.c | 7 +++++++
 compiler/src/CFCClass.h | 3 +++
 5 files changed, 19 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/2a44a5b3/compiler/src/CFCC.c
----------------------------------------------------------------------
diff --git a/compiler/src/CFCC.c b/compiler/src/CFCC.c
index 6197e2e..10fec57 100644
--- a/compiler/src/CFCC.c
+++ b/compiler/src/CFCC.c
@@ -92,7 +92,8 @@ CFCC_write_man_pages(CFCC *self) {
     size_t num_classes = 0;
     for (size_t i = 0; ordered[i] != NULL; i++) {
         CFCClass *klass = ordered[i];
-        if (!CFCClass_included(klass)) { ++num_classes; }
+        if (!CFCClass_needs_documentation(klass)) { continue; }
+        ++num_classes;
     }
     char **man_pages = (char**)CALLOCATE(num_classes, sizeof(char*));
 
@@ -101,7 +102,7 @@ CFCC_write_man_pages(CFCC *self) {
     // system.
     for (size_t i = 0, j = 0; ordered[i] != NULL; i++) {
         CFCClass *klass = ordered[i];
-        if (CFCClass_included(klass)) { continue; }
+        if (!CFCClass_needs_documentation(klass)) { continue; }
 
         char *man_page = CFCCMan_create_man_page(klass);
         man_pages[j++] = man_page;
@@ -114,10 +115,9 @@ CFCC_write_man_pages(CFCC *self) {
     // Write out any man pages that have changed.
     for (size_t i = 0, j = 0; ordered[i] != NULL; i++) {
         CFCClass *klass = ordered[i];
-        if (CFCClass_included(klass)) { continue; }
+        if (!CFCClass_needs_documentation(klass)) { continue; }
 
         char *raw_man_page = man_pages[j++];
-        if (!raw_man_page) { continue; }
         char *man_page = CFCUtil_sprintf("%s%s%s", self->man_header,
                                          raw_man_page, self->man_footer);
 

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/2a44a5b3/compiler/src/CFCCHtml.c
----------------------------------------------------------------------
diff --git a/compiler/src/CFCCHtml.c b/compiler/src/CFCCHtml.c
index e527c53..6d5f908 100644
--- a/compiler/src/CFCCHtml.c
+++ b/compiler/src/CFCCHtml.c
@@ -268,9 +268,7 @@ CFCCHtml_write_html_docs(CFCCHtml *self) {
 
     for (size_t i = 0; ordered[i] != NULL; i++) {
         CFCClass *klass = ordered[i];
-        if (CFCClass_included(klass) || !CFCClass_public(klass)) {
-            continue;
-        }
+        if (!CFCClass_needs_documentation(klass)) { continue; }
 
         const char *class_name = CFCClass_get_name(klass);
         char *path = CFCUtil_global_replace(class_name, "::", CHY_DIR_SEP);
@@ -341,6 +339,7 @@ S_create_index_doc(CFCCHtml *self, CFCClass **classes, CFCDocument **docs) {
     }
 
     // Compile class lists per parcel.
+    // TODO: Sort parcels. Don't derive filename from random parcel.
 
     char *class_lists    = CFCUtil_strdup("");
     char *parcel_names   = CFCUtil_strdup("");
@@ -348,7 +347,9 @@ S_create_index_doc(CFCCHtml *self, CFCClass **classes, CFCDocument **docs) {
 
     for (size_t i = 0; parcels[i]; i++) {
         CFCParcel *parcel = parcels[i];
-        if (CFCParcel_included(parcel)) { continue; }
+        if (CFCParcel_included(parcel) || !CFCParcel_is_installed(parcel)) {
+            continue;
+        }
 
         const char *prefix      = CFCParcel_get_prefix(parcel);
         const char *parcel_name = CFCParcel_get_name(parcel);

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/2a44a5b3/compiler/src/CFCCMan.c
----------------------------------------------------------------------
diff --git a/compiler/src/CFCCMan.c b/compiler/src/CFCCMan.c
index 771d410..b271702 100644
--- a/compiler/src/CFCCMan.c
+++ b/compiler/src/CFCCMan.c
@@ -77,8 +77,6 @@ S_man_escape(const char *content);
 
 char*
 CFCCMan_create_man_page(CFCClass *klass) {
-    if (!CFCClass_public(klass)) { return NULL; }
-
     const char *class_name = CFCClass_get_name(klass);
 
     // Create NAME.

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/2a44a5b3/compiler/src/CFCClass.c
----------------------------------------------------------------------
diff --git a/compiler/src/CFCClass.c b/compiler/src/CFCClass.c
index ea39848..1fb2041 100644
--- a/compiler/src/CFCClass.c
+++ b/compiler/src/CFCClass.c
@@ -887,6 +887,13 @@ CFCClass_abstract(CFCClass *self) {
     return self->is_abstract;
 }
 
+int
+CFCClass_needs_documentation(CFCClass *self) {
+    return CFCClass_public(self)
+           && !CFCClass_included(self)
+           && CFCParcel_is_installed(self->parcel);
+}
+
 const char*
 CFCClass_get_struct_sym(CFCClass *self) {
     return self->struct_sym;

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/2a44a5b3/compiler/src/CFCClass.h
----------------------------------------------------------------------
diff --git a/compiler/src/CFCClass.h b/compiler/src/CFCClass.h
index 91fa0cf..bf86f61 100644
--- a/compiler/src/CFCClass.h
+++ b/compiler/src/CFCClass.h
@@ -255,6 +255,9 @@ CFCClass_inert(CFCClass *self);
 int
 CFCClass_abstract(CFCClass *self);
 
+int
+CFCClass_needs_documentation(CFCClass *self);
+
 const char*
 CFCClass_get_struct_sym(CFCClass *self);