Changeset 148257 in webkit


Ignore:
Timestamp:
Apr 11, 2013 6:43:23 PM (11 years ago)
Author:
oliver@apple.com
Message:

Add more type validation to debug builds
https://bugs.webkit.org/show_bug.cgi?id=114478

Reviewed by Mark Hahnenberg.

Source/WebCore:

Add a bunch more type checks to the JS DOM bindings.

  • Modules/mediastream/MediaStream.idl:
  • Modules/webaudio/AudioDestinationNode.idl:
  • WebCore.xcodeproj/project.pbxproj:
  • bindings/js/JSDOMBinding.h:

(WebCore::getExistingWrapper):
(WebCore):
(WebCore::createNewWrapper):

  • bindings/scripts/CodeGeneratorJS.pm:

(GetNativeTypeForConversions):
(GetGnuVTableRefForInterface):
(GetGnuVTableNameForInterface):
(GetGnuMangledNameForInterface):
(GetGnuVTableOffsetForType):
(GetWinVTableRefForInterface):
(GetWinVTableNameForInterface):
(GetWinMangledNameForInterface):
(GetNamespaceForInterface):
(GetImplementationLacksVTableForInterface):
(GetSkipVTableValidationForInterface):
(GenerateImplementation):

  • bindings/scripts/IDLAttributes.txt:
  • css/CSSRuleList.idl:
  • css/CSSStyleDeclaration.idl:
  • dom/Clipboard.idl:
  • dom/DOMStringMap.idl:
  • dom/MutationRecord.idl:
  • dom/NodeList.idl:
  • html/DOMTokenList.idl:
  • html/track/TextTrack.idl:
  • inspector/ScriptProfileNode.idl:
  • storage/Storage.idl:
  • xml/XPathNSResolver.idl:

Source/WTF:

Add BINDING_VALIDATION flag and make RELEASE_ASSERT use UNLIKELY.

  • wtf/Assertions.h:
  • wtf/Platform.h:
Location:
trunk/Source
Files:
21 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WTF/ChangeLog

    r148166 r148257  
     12013-04-11  Oliver Hunt  <oliver@apple.com>
     2
     3        Add more type validation to debug builds
     4        https://bugs.webkit.org/show_bug.cgi?id=114478
     5
     6        Reviewed by Mark Hahnenberg.
     7
     8        Add BINDING_VALIDATION flag and make RELEASE_ASSERT use UNLIKELY.
     9
     10        * wtf/Assertions.h:
     11        * wtf/Platform.h:
     12
    1132013-04-10  Thiago Marcos P. Santos  <thiago.santos@intel.com>
    214
  • trunk/Source/WTF/wtf/Assertions.h

    r146993 r148257  
    417417
    418418#if ASSERT_DISABLED
    419 #define RELEASE_ASSERT(assertion) (!(assertion) ? (CRASH()) : (void)0)
     419#define RELEASE_ASSERT(assertion) (UNLIKELY(!(assertion)) ? (CRASH()) : (void)0)
    420420#define RELEASE_ASSERT_WITH_MESSAGE(assertion, ...) RELEASE_ASSERT(assertion)
    421421#define RELEASE_ASSERT_NOT_REACHED() CRASH()
  • trunk/Source/WTF/wtf/Platform.h

    r147937 r148257  
    966966#endif
    967967
     968#if !defined(ENABLE_BINDING_INTEGRITY)
     969#define ENABLE_BINDING_INTEGRITY 1
     970#endif
     971
    968972#if PLATFORM(MAC) && !PLATFORM(IOS) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
    969973#define WTF_USE_AVFOUNDATION 1
  • trunk/Source/WebCore/ChangeLog

    r148256 r148257  
     12013-04-11  Oliver Hunt  <oliver@apple.com>
     2
     3        Add more type validation to debug builds
     4        https://bugs.webkit.org/show_bug.cgi?id=114478
     5
     6        Reviewed by Mark Hahnenberg.
     7
     8        Add a bunch more type checks to the JS DOM bindings.
     9
     10        * Modules/mediastream/MediaStream.idl:
     11        * Modules/webaudio/AudioDestinationNode.idl:
     12        * WebCore.xcodeproj/project.pbxproj:
     13        * bindings/js/JSDOMBinding.h:
     14        (WebCore::getExistingWrapper):
     15        (WebCore):
     16        (WebCore::createNewWrapper):
     17        * bindings/scripts/CodeGeneratorJS.pm:
     18        (GetNativeTypeForConversions):
     19        (GetGnuVTableRefForInterface):
     20        (GetGnuVTableNameForInterface):
     21        (GetGnuMangledNameForInterface):
     22        (GetGnuVTableOffsetForType):
     23        (GetWinVTableRefForInterface):
     24        (GetWinVTableNameForInterface):
     25        (GetWinMangledNameForInterface):
     26        (GetNamespaceForInterface):
     27        (GetImplementationLacksVTableForInterface):
     28        (GetSkipVTableValidationForInterface):
     29        (GenerateImplementation):
     30        * bindings/scripts/IDLAttributes.txt:
     31        * css/CSSRuleList.idl:
     32        * css/CSSStyleDeclaration.idl:
     33        * dom/Clipboard.idl:
     34        * dom/DOMStringMap.idl:
     35        * dom/MutationRecord.idl:
     36        * dom/NodeList.idl:
     37        * html/DOMTokenList.idl:
     38        * html/track/TextTrack.idl:
     39        * inspector/ScriptProfileNode.idl:
     40        * storage/Storage.idl:
     41        * xml/XPathNSResolver.idl:
     42
    1432013-04-11  Ryosuke Niwa  <rniwa@webkit.org>
    244
  • trunk/Source/WebCore/Modules/mediastream/MediaStream.idl

    r142177 r148257  
    3131    ConstructorParameters=0,
    3232    CallWith=ScriptExecutionContext,
    33     V8SkipVTableValidation
     33    SkipVTableValidation
    3434] interface MediaStream {
    3535    // DEPRECATED
  • trunk/Source/WebCore/Modules/webaudio/AudioDestinationNode.idl

    r144720 r148257  
    2626    Conditional=WEB_AUDIO,
    2727    JSGenerateToJSObject,
    28     V8SkipVTableValidation
     28    SkipVTableValidation
    2929] interface AudioDestinationNode : AudioNode {
    3030    readonly attribute unsigned long maxChannelCount;
  • trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj

    r148256 r148257  
    99859985                A7DBF8DB1276919C006B6008 /* TextCheckingHelper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextCheckingHelper.cpp; sourceTree = "<group>"; };
    99869986                A7DBF8DC1276919C006B6008 /* TextCheckingHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextCheckingHelper.h; sourceTree = "<group>"; };
     9987                A7F1F4BA17148BDB00CD4852 /* DOMWindowQuota.idl */ = {isa = PBXFileReference; lastKnownFileType = text; name = DOMWindowQuota.idl; path = Modules/quota/DOMWindowQuota.idl; sourceTree = SOURCE_ROOT; };
     9988                A7F1F4BB17148BDB00CD4852 /* NavigatorStorageQuota.idl */ = {isa = PBXFileReference; lastKnownFileType = text; name = NavigatorStorageQuota.idl; path = Modules/quota/NavigatorStorageQuota.idl; sourceTree = SOURCE_ROOT; };
     9989                A7F1F4BC17148BDB00CD4852 /* StorageErrorCallback.idl */ = {isa = PBXFileReference; lastKnownFileType = text; name = StorageErrorCallback.idl; path = Modules/quota/StorageErrorCallback.idl; sourceTree = SOURCE_ROOT; };
     9990                A7F1F4BD17148BDB00CD4852 /* StorageInfo.idl */ = {isa = PBXFileReference; lastKnownFileType = text; name = StorageInfo.idl; path = Modules/quota/StorageInfo.idl; sourceTree = SOURCE_ROOT; };
     9991                A7F1F4BE17148BDB00CD4852 /* StorageQuota.idl */ = {isa = PBXFileReference; lastKnownFileType = text; name = StorageQuota.idl; path = Modules/quota/StorageQuota.idl; sourceTree = SOURCE_ROOT; };
     9992                A7F1F4BF17148BDB00CD4852 /* StorageQuotaCallback.idl */ = {isa = PBXFileReference; lastKnownFileType = text; name = StorageQuotaCallback.idl; path = Modules/quota/StorageQuotaCallback.idl; sourceTree = SOURCE_ROOT; };
     9993                A7F1F4C017148BDB00CD4852 /* StorageUsageCallback.idl */ = {isa = PBXFileReference; lastKnownFileType = text; name = StorageUsageCallback.idl; path = Modules/quota/StorageUsageCallback.idl; sourceTree = SOURCE_ROOT; };
     9994                A7F1F4C117148BDB00CD4852 /* WorkerNavigatorStorageQuota.idl */ = {isa = PBXFileReference; lastKnownFileType = text; name = WorkerNavigatorStorageQuota.idl; path = Modules/quota/WorkerNavigatorStorageQuota.idl; sourceTree = SOURCE_ROOT; };
    99879995                A7F5D94D1384F02D00A29A87 /* NodeRenderingContext.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = NodeRenderingContext.cpp; sourceTree = "<group>"; };
    99889996                A7F5D94E1384F02D00A29A87 /* NodeRenderingContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NodeRenderingContext.h; sourceTree = "<group>"; };
     
    1547115479                        isa = PBXGroup;
    1547215480                        children = (
     15481                                A7F1F4BA17148BDB00CD4852 /* DOMWindowQuota.idl */,
     15482                                A7F1F4BB17148BDB00CD4852 /* NavigatorStorageQuota.idl */,
     15483                                A7F1F4BC17148BDB00CD4852 /* StorageErrorCallback.idl */,
     15484                                A7F1F4BD17148BDB00CD4852 /* StorageInfo.idl */,
     15485                                A7F1F4BE17148BDB00CD4852 /* StorageQuota.idl */,
     15486                                A7F1F4BF17148BDB00CD4852 /* StorageQuotaCallback.idl */,
     15487                                A7F1F4C017148BDB00CD4852 /* StorageUsageCallback.idl */,
     15488                                A7F1F4C117148BDB00CD4852 /* WorkerNavigatorStorageQuota.idl */,
    1547315489                                892ABE5F16EEE2E5009F3587 /* JSDOMWindowQuota.h */,
    1547415490                                892ABE5C16EEE2AA009F3587 /* JSNavigatorStorageQuota.cpp */,
  • trunk/Source/WebCore/bindings/js/JSDOMBinding.h

    r147962 r148257  
    201201    }
    202202
     203    template<class WrapperClass, class DOMClass> inline JSC::JSValue getExistingWrapper(JSC::ExecState* exec, DOMClass* domObject)
     204    {
     205        ASSERT(domObject);
     206        return getCachedWrapper(currentWorld(exec), domObject);
     207    }
     208
     209    template<class WrapperClass, class DOMClass> inline JSC::JSValue createNewWrapper(JSC::ExecState* exec, JSDOMGlobalObject* globalObject, DOMClass* domObject)
     210    {
     211        ASSERT(domObject);
     212        ASSERT(!getCachedWrapper(currentWorld(exec), domObject));
     213        return createWrapper<WrapperClass>(exec, globalObject, domObject);
     214    }
     215
    203216    inline JSC::JSValue argumentOrNull(JSC::ExecState* exec, unsigned index)
    204217    {
  • trunk/Source/WebCore/bindings/scripts/CodeGeneratorJS.pm

    r147480 r148257  
    1010# Copyright (C) 2011 Patrick Gansterer <paroga@webkit.org>
    1111# Copyright (C) 2012 Ericsson AB. All rights reserved.
     12# Copyright (C) 2007, 2008, 2009, 2012 Google Inc.
    1213#
    1314# This library is free software; you can redistribute it and/or
     
    13961397}
    13971398
     1399sub GetNativeTypeForConversions
     1400{
     1401    my $interface = shift;
     1402    my $interfaceName = $interface->name;
     1403    $interfaceName = $codeGenerator->GetSVGTypeNeedingTearOff($interfaceName) if $codeGenerator->IsSVGTypeNeedingTearOff($interfaceName);
     1404    return $interfaceName;
     1405}
     1406
     1407# See http://refspecs.linux-foundation.org/cxxabi-1.83.html.
     1408sub GetGnuVTableRefForInterface
     1409{
     1410    my $interface = shift;
     1411    my $vtableName = GetGnuVTableNameForInterface($interface);
     1412    if (!$vtableName) {
     1413        return "0";
     1414    }
     1415    my $typename = GetNativeTypeForConversions($interface);
     1416    my $offset = GetGnuVTableOffsetForType($typename);
     1417    return "&" . $vtableName . "[" . $offset . "]";
     1418}
     1419
     1420sub GetGnuVTableNameForInterface
     1421{
     1422    my $interface = shift;
     1423    my $typename = GetNativeTypeForConversions($interface);
     1424    my $templatePosition = index($typename, "<");
     1425    return "" if $templatePosition != -1;
     1426    return "" if GetImplementationLacksVTableForInterface($interface);
     1427    return "" if GetSkipVTableValidationForInterface($interface);
     1428    return "_ZTV" . GetGnuMangledNameForInterface($interface);
     1429}
     1430
     1431sub GetGnuMangledNameForInterface
     1432{
     1433    my $interface = shift;
     1434    my $typename = GetNativeTypeForConversions($interface);
     1435    my $templatePosition = index($typename, "<");
     1436    if ($templatePosition != -1) {
     1437        return "";
     1438    }
     1439    my $mangledType = length($typename) . $typename;
     1440    my $namespace = GetNamespaceForInterface($interface);
     1441    my $mangledNamespace =  "N" . length($namespace) . $namespace;
     1442    return $mangledNamespace . $mangledType . "E";
     1443}
     1444
     1445sub GetGnuVTableOffsetForType
     1446{
     1447    my $typename = shift;
     1448    if ($typename eq "SVGAElement"
     1449        || $typename eq "SVGCircleElement"
     1450        || $typename eq "SVGClipPathElement"
     1451        || $typename eq "SVGDefsElement"
     1452        || $typename eq "SVGEllipseElement"
     1453        || $typename eq "SVGForeignObjectElement"
     1454        || $typename eq "SVGGElement"
     1455        || $typename eq "SVGImageElement"
     1456        || $typename eq "SVGLineElement"
     1457        || $typename eq "SVGPathElement"
     1458        || $typename eq "SVGPolyElement"
     1459        || $typename eq "SVGPolygonElement"
     1460        || $typename eq "SVGPolylineElement"
     1461        || $typename eq "SVGRectElement"
     1462        || $typename eq "SVGSVGElement"
     1463        || $typename eq "SVGStyledLocatableElement"
     1464        || $typename eq "SVGStyledTransformableElement"
     1465        || $typename eq "SVGSwitchElement"
     1466        || $typename eq "SVGTextElement"
     1467        || $typename eq "SVGTransformable"
     1468        || $typename eq "SVGUseElement") {
     1469        return "3";
     1470    }
     1471    return "2";
     1472}
     1473
     1474# See http://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B_Name_Mangling.
     1475sub GetWinVTableRefForInterface
     1476{
     1477    my $interface = shift;
     1478    my $vtableName = GetWinVTableNameForInterface($interface);
     1479    return 0 if !$vtableName;
     1480    return "__identifier(\"" . $vtableName . "\")";
     1481}
     1482
     1483sub GetWinVTableNameForInterface
     1484{
     1485    my $interface = shift;
     1486    my $typename = GetNativeTypeForConversions($interface);
     1487    my $templatePosition = index($typename, "<");
     1488    return "" if $templatePosition != -1;
     1489    return "" if GetImplementationLacksVTableForInterface($interface);
     1490    return "" if GetSkipVTableValidationForInterface($interface);
     1491    return "??_7" . GetWinMangledNameForInterface($interface) . "6B@";
     1492}
     1493
     1494sub GetWinMangledNameForInterface
     1495{
     1496    my $interface = shift;
     1497    my $typename = GetNativeTypeForConversions($interface);
     1498    my $namespace = GetNamespaceForInterface($interface);
     1499    return $typename . "@" . $namespace . "@@";
     1500}
     1501
     1502sub GetNamespaceForInterface
     1503{
     1504    my $interface = shift;
     1505    return $interface->extendedAttributes->{"ImplementationNamespace"} || "WebCore";
     1506}
     1507
     1508sub GetImplementationLacksVTableForInterface
     1509{
     1510    my $interface = shift;
     1511    return $interface->extendedAttributes->{"ImplementationLacksVTable"};
     1512}
     1513
     1514sub GetSkipVTableValidationForInterface
     1515{
     1516    my $interface = shift;
     1517    return $interface->extendedAttributes->{"SkipVTableValidation"};
     1518}
     1519
     1520
    13981521sub GenerateImplementation
    13991522{
     
    19232046
    19242047                        unshift(@arguments, @callWithArgs);
    1925 
    19262048                        my $jsType = NativeToJSValue($attribute->signature, 0, $interfaceName, "${functionName}(" . join(", ", @arguments) . ")", "castedThis");
    19272049                        push(@implContent, "    $interfaceName* impl = static_cast<$interfaceName*>(castedThis->impl());\n") if !$attribute->isStatic;
     
    25792701
    25802702    if (ShouldGenerateToJSImplementation($hasParent, $interface)) {
     2703        my $vtableNameGnu = GetGnuVTableNameForInterface($interface);
     2704        my $vtableRefGnu = GetGnuVTableRefForInterface($interface);
     2705        my $vtableRefWin = GetWinVTableRefForInterface($interface);
     2706
     2707        push(@implContent, <<END) if $vtableNameGnu;
     2708#if ENABLE(BINDING_INTEGRITY)
     2709#if defined(OS_WIN)
     2710#pragma warning(disable: 4483)
     2711extern "C" { extern void (*const ${vtableRefWin}[])(); }
     2712#else
     2713extern "C" { extern void* ${vtableNameGnu}[]; }
     2714#endif
     2715#endif
     2716END
    25812717        push(@implContent, "JSC::JSValue toJS(JSC::ExecState* exec, JSDOMGlobalObject* globalObject, $implType* impl)\n");
    25822718        push(@implContent, "{\n");
     2719        push(@implContent, <<END);
     2720    if (!impl)
     2721        return jsNull();
     2722END
     2723
    25832724        if ($svgPropertyType) {
    2584             push(@implContent, "    return wrap<$className, $implType>(exec, globalObject, impl);\n");
     2725            push(@implContent, "    if (JSValue result = getExistingWrapper<$className, $implType>(exec, impl)) return result;\n");
    25852726        } else {
    2586             push(@implContent, "    return wrap<$className>(exec, globalObject, impl);\n");
    2587         }
     2727            push(@implContent, "    if (JSValue result = getExistingWrapper<$className>(exec, impl)) return result;\n");
     2728        }
     2729        push(@implContent, <<END) if $vtableNameGnu;
     2730
     2731#if ENABLE(BINDING_INTEGRITY)
     2732    void* actualVTablePointer = *(reinterpret_cast<void**>(impl));
     2733#if defined(OS_WIN)
     2734    void* expectedVTablePointer = reinterpret_cast<void*>(${vtableRefWin});
     2735#else
     2736    void* expectedVTablePointer = ${vtableRefGnu};
     2737#if COMPILER(CLANG)
     2738    COMPILE_ASSERT(__is_polymorphic($implType), ${implType}_is_not_polymorphic);
     2739#endif
     2740#endif
     2741    RELEASE_ASSERT(actualVTablePointer == expectedVTablePointer);
     2742#endif
     2743END
     2744        push(@implContent, <<END) if $interface->extendedAttributes->{"ImplementationLacksVTable"} && $vtableNameGnu;
     2745#if COMPILER(CLANG)
     2746        COMPILE_ASSERT(!__is_polymorphic($implType), ${implType}_is_polymorphic_but_idl_claims_not_to_be);
     2747#endif
     2748END
     2749
     2750        if ($svgPropertyType) {
     2751            push(@implContent, "    return createNewWrapper<$className, $implType>(exec, globalObject, impl);\n");
     2752        } else {
     2753            push(@implContent, "    return createNewWrapper<$className>(exec, globalObject, impl);\n");
     2754        }
     2755
    25882756        push(@implContent, "}\n\n");
    25892757    }
  • trunk/Source/WebCore/bindings/scripts/IDLAttributes.txt

    r146583 r148257  
    105105ReplaceableConstructor
    106106ReturnNewObject
     107SkipVTableValidation
    107108StrictTypeChecking
    108109Supplemental=*
     
    131132V8PerWorldBindings
    132133V8ReadOnly
    133 V8SkipVTableValidation
    134134V8Unforgeable
    135135V8WrapAsFunction
  • trunk/Source/WebCore/css/CSSRuleList.idl

    r147857 r148257  
    2828    JSCustomIsReachable,
    2929    IndexedGetter,
     30    SkipVTableValidation
    3031] interface CSSRuleList {
    3132    readonly attribute unsigned long    length;
  • trunk/Source/WebCore/css/CSSStyleDeclaration.idl

    r147857 r148257  
    2828    IndexedGetter,
    2929    CustomEnumerateProperty,
     30    SkipVTableValidation
    3031] interface CSSStyleDeclaration {
    3132             [TreatReturnedNullStringAs=Null, TreatNullAs=NullString] attribute DOMString        cssText
  • trunk/Source/WebCore/dom/Clipboard.idl

    r147857 r148257  
    2626 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    2727 */
    28 
    29 interface Clipboard {
     28[
     29    SkipVTableValidation
     30] interface Clipboard {
    3031             [TreatReturnedNullStringAs=Undefined] attribute DOMString dropEffect;
    3132             [TreatReturnedNullStringAs=Undefined] attribute DOMString effectAllowed;
  • trunk/Source/WebCore/dom/DOMStringMap.idl

    r147857 r148257  
    3030    CustomEnumerateProperty,
    3131    CustomNamedSetter,
     32    SkipVTableValidation
    3233] interface DOMStringMap {
    3334};
  • trunk/Source/WebCore/dom/MutationRecord.idl

    r147857 r148257  
    2929 */
    3030
    31 interface MutationRecord {
     31[   
     32    SkipVTableValidation
     33] interface MutationRecord {
    3234    readonly attribute DOMString type;
    3335    readonly attribute Node target;
  • trunk/Source/WebCore/dom/NodeList.idl

    r147857 r148257  
    2323    IndexedGetter,
    2424    NamedGetter,
     25    SkipVTableValidation
    2526] interface NodeList {
    2627
  • trunk/Source/WebCore/html/DOMTokenList.idl

    r147857 r148257  
    2626    GenerateIsReachable=ImplElementRoot,
    2727    IndexedGetter,
     28    SkipVTableValidation
    2829] interface DOMTokenList {
    2930    readonly attribute unsigned long length;
  • trunk/Source/WebCore/html/track/TextTrack.idl

    r147857 r148257  
    2929    JSCustomMarkFunction,
    3030    JSCustomIsReachable,
     31    SkipVTableValidation
    3132] interface TextTrack {
    3233    readonly attribute DOMString kind;
  • trunk/Source/WebCore/inspector/ScriptProfileNode.idl

    r140185 r148257  
    2727[
    2828    Conditional=JAVASCRIPT_DEBUGGER,
    29     OmitConstructor
     29    OmitConstructor,
     30    ImplementationLacksVTable
    3031] interface ScriptProfileNode {
    3132    readonly attribute DOMString functionName;
  • trunk/Source/WebCore/storage/Storage.idl

    r144419 r148257  
    3131    V8CustomIndexedGetter,
    3232    CustomNamedSetter,
     33    SkipVTableValidation
    3334] interface Storage {
    3435    [NotEnumerable] readonly attribute unsigned long length getter raises(DOMException);
  • trunk/Source/WebCore/xml/XPathNSResolver.idl

    r141034 r148257  
    2222    ObjCProtocol,
    2323    OmitConstructor,
    24     V8SkipVTableValidation
     24    SkipVTableValidation
    2525] interface XPathNSResolver {
    2626    [TreatReturnedNullStringAs=Null] DOMString lookupNamespaceURI(in [Optional=DefaultIsUndefined] DOMString prefix);
Note: See TracChangeset for help on using the changeset viewer.