Changeset 3403534 for src/InitTweak
- Timestamp:
- Sep 4, 2016, 10:34:35 PM (9 years ago)
- Branches:
- ADT, aaron-thesis, arm-eh, ast-experimental, cleanup-dtors, ctor, deferred_resn, demangler, enum, forall-pointer-decay, jacob/cs343-translation, jenkins-sandbox, master, new-ast, new-ast-unique-expr, new-env, no_list, persistent-indexer, pthread-emulation, qualifiedEnum, resolv-new, with_gc
- Children:
- f04a8b81
- Parents:
- 28307be (diff), b16898e (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the(diff)links above to see all the changes relative to each parent. - Location:
- src/InitTweak
- Files:
-
- 13 deleted
- 4 edited
-
Association.cc (deleted)
-
Association.h (deleted)
-
BasicInit.cc (deleted)
-
BasicInit.h (deleted)
-
DeclarationHoister.cc (deleted)
-
DeclarationHoister.h (deleted)
-
FixInit.cc (modified) (15 diffs)
-
GenInit.cc (modified) (9 diffs)
-
InitExpander.cc (deleted)
-
InitExpander.h (deleted)
-
InitModel.cc (deleted)
-
InitModel.h (deleted)
-
InitTweak.cc (modified) (6 diffs)
-
InitTweak.h (modified) (1 diff)
-
Mutate.cc (deleted)
-
Mutate.h (deleted)
-
diet_map.h (deleted)
Legend:
- Unmodified
- Added
- Removed
-
src/InitTweak/FixInit.cc
r28307be r3403534 31 31 #include "SynTree/Mutator.h" 32 32 #include "SymTab/Indexer.h" 33 #include "SymTab/Autogen.h" 33 34 #include "GenPoly/PolyMutator.h" 34 35 #include "SynTree/AddStmtVisitor.h" … … 176 177 }; 177 178 178 class WarnStructMembers : public Visitor {179 class GenStructMemberCalls : public SymTab::Indexer { 179 180 public: 180 typedef Visitor Parent; 181 /// warn if a user-defined constructor or destructor is missing calls for 182 /// a struct member or if a member is used before constructed 183 static void warnings( std::list< Declaration * > & translationUnit ); 181 typedef Indexer Parent; 182 /// generate default/copy ctor and dtor calls for user-defined struct ctor/dtors 183 /// for any member that is missing a corresponding ctor/dtor call. 184 /// error if a member is used before constructed 185 static void generate( std::list< Declaration * > & translationUnit ); 184 186 185 187 virtual void visit( FunctionDecl * funcDecl ); … … 188 190 virtual void visit( ApplicationExpr * appExpr ); 189 191 192 SemanticError errors; 190 193 private: 191 194 void handleFirstParam( Expression * firstParam ); 195 template< typename... Params > 196 void emit( const Params &... params ); 192 197 193 198 FunctionDecl * function = 0; 194 std::set< DeclarationWithType * > unhandled ;199 std::set< DeclarationWithType * > unhandled, usedUninit; 195 200 ObjectDecl * thisParam = 0; 201 bool isCtor = false; // true if current function is a constructor 202 StructDecl * structDecl = 0; 203 }; 204 205 // very simple resolver-like mutator class - used to 206 // resolve UntypedExprs that are found within newly 207 // generated constructor/destructor calls 208 class MutatingResolver : public Mutator { 209 public: 210 MutatingResolver( SymTab::Indexer & indexer ) : indexer( indexer ) {} 211 212 virtual DeclarationWithType* mutate( ObjectDecl *objectDecl ); 213 214 virtual Expression* mutate( UntypedExpr *untypedExpr ); 215 private: 216 SymTab::Indexer & indexer; 196 217 }; 197 218 } // namespace … … 209 230 FixCopyCtors::fixCopyCtors( translationUnit ); 210 231 211 WarnStructMembers::warnings( translationUnit );232 GenStructMemberCalls::generate( translationUnit ); 212 233 } 213 234 … … 254 275 } 255 276 256 void WarnStructMembers::warnings( std::list< Declaration * > & translationUnit ) { 257 if ( true ) { // fix this condition to skip this pass if warnings aren't enabled 258 WarnStructMembers warner; 259 acceptAll( translationUnit, warner ); 277 void GenStructMemberCalls::generate( std::list< Declaration * > & translationUnit ) { 278 GenStructMemberCalls warner; 279 acceptAll( translationUnit, warner ); 280 281 // visitor doesn't throw so that it can collect all errors 282 if ( ! warner.errors.isEmpty() ) { 283 throw warner.errors; 260 284 } 261 285 } … … 528 552 FunctionDecl * dtorCaller = new FunctionDecl( objDecl->get_mangleName() + dtorCallerNamer.newName(), DeclarationNode::Static, LinkageSpec::C, new FunctionType( Type::Qualifiers(), false ), new CompoundStmt( noLabels ), false, false ); 529 553 dtorCaller->fixUniqueId(); 530 dtorCaller->get_statements()-> get_kids().push_back( dtorStmt );554 dtorCaller->get_statements()->push_back( dtorStmt ); 531 555 532 556 // atexit(dtor_atexit); … … 543 567 // at global scope and there could be multiple function-scoped 544 568 // static variables with the same name in different functions. 569 // Note: it isn't sufficient to modify only the mangleName, because 570 // then subsequent Indexer passes can choke on seeing the object's name 571 // if another object has the same name and type. An unfortunate side-effect 572 // of renaming the object is that subsequent NameExprs may fail to resolve, 573 // but there shouldn't be any remaining past this point. 545 574 static UniqueName staticNamer( "_static_var" ); 546 objDecl->set_mangleName( objDecl->get_mangleName() + staticNamer.newName() ); 575 objDecl->set_name( objDecl->get_name() + staticNamer.newName() ); 576 objDecl->set_mangleName( SymTab::Mangler::mangle( objDecl ) ); 547 577 548 578 objDecl->set_init( NULL ); … … 709 739 } 710 740 711 void WarnStructMembers::visit( FunctionDecl * funcDecl ) { 712 WarnStructMembers old = *this; 713 *this = WarnStructMembers(); 741 void GenStructMemberCalls::visit( FunctionDecl * funcDecl ) { 742 ValueGuard< FunctionDecl * > oldFunction( funcDecl ); 743 ValueGuard< std::set< DeclarationWithType * > > oldUnhandled( unhandled ); 744 ValueGuard< std::set< DeclarationWithType * > > oldUsedUninit( usedUninit ); 745 ValueGuard< ObjectDecl * > oldThisParam( thisParam ); 746 ValueGuard< bool > oldIsCtor( isCtor ); 747 ValueGuard< StructDecl * > oldStructDecl( structDecl ); 748 749 // need to start with fresh sets 750 unhandled.clear(); 751 usedUninit.clear(); 714 752 715 753 function = funcDecl; 716 if ( checkWarnings( funcDecl ) ) { 717 FunctionType * type = funcDecl->get_functionType(); 754 isCtor = isConstructor( function->get_name() ); 755 if ( checkWarnings( function ) ) { 756 FunctionType * type = function->get_functionType(); 718 757 assert( ! type->get_parameters().empty() ); 719 758 thisParam = safe_dynamic_cast< ObjectDecl * >( type->get_parameters().front() ); … … 721 760 StructInstType * structType = dynamic_cast< StructInstType * >( ptrType->get_base() ); 722 761 if ( structType ) { 723 StructDecl *structDecl = structType->get_baseStruct();762 structDecl = structType->get_baseStruct(); 724 763 for ( Declaration * member : structDecl->get_members() ) { 725 764 if ( ObjectDecl * field = dynamic_cast< ObjectDecl * >( member ) ) { … … 731 770 } 732 771 } 733 Parent::visit( funcDecl ); 734 735 for ( DeclarationWithType * member : unhandled ) { 736 // emit a warning for each unhandled member 737 warn( "in ", CodeGen::genType( function->get_functionType(), function->get_name(), false ), ", member ", member->get_name(), " may not have been ", isConstructor( funcDecl->get_name() ) ? "constructed" : "destructed" ); 772 Parent::visit( function ); 773 774 // remove the unhandled objects from usedUninit, because a call is inserted 775 // to handle them - only objects that are later constructed are used uninitialized. 776 std::set< DeclarationWithType * > diff; 777 std::set_difference( usedUninit.begin(), usedUninit.end(), unhandled.begin(), unhandled.end(), std::inserter( diff, diff.begin() ) ); 778 for ( DeclarationWithType * member : diff ) { 779 emit( "in ", CodeGen::genType( function->get_functionType(), function->get_name(), false ), ", field ", member->get_name(), " used before being constructed" ); 738 780 } 739 781 740 *this = old; 741 } 742 743 void WarnStructMembers::visit( ApplicationExpr * appExpr ) { 782 if ( ! unhandled.empty() ) { 783 // need to explicitly re-add function parameters in order to resolve copy constructors 784 enterScope(); 785 maybeAccept( function->get_functionType(), *this ); 786 787 // need to iterate through members in reverse in order for 788 // ctor/dtor statements to come out in the right order 789 for ( Declaration * member : reverseIterate( structDecl->get_members() ) ) { 790 DeclarationWithType * field = dynamic_cast< DeclarationWithType * >( member ); 791 // skip non-DWT members 792 if ( ! field ) continue; 793 // skip handled members 794 if ( ! unhandled.count( field ) ) continue; 795 796 // insert and resolve default/copy constructor call for each field that's unhandled 797 std::list< Statement * > stmt; 798 UntypedExpr * deref = new UntypedExpr( new NameExpr( "*?" ) ); 799 deref->get_args().push_back( new VariableExpr( thisParam ) ); 800 801 Expression * arg2 = 0; 802 if ( isCopyConstructor( function ) ) { 803 // if copy ctor, need to pass second-param-of-this-function.field 804 std::list< DeclarationWithType * > & params = function->get_functionType()->get_parameters(); 805 assert( params.size() == 2 ); 806 arg2 = new MemberExpr( field, new VariableExpr( params.back() ) ); 807 } 808 InitExpander srcParam( arg2 ); 809 SymTab::genImplicitCall( srcParam, new MemberExpr( field, deref ), function->get_name(), back_inserter( stmt ), field, isCtor ); 810 811 assert( stmt.size() <= 1 ); 812 if ( stmt.size() == 1 ) { 813 Statement * callStmt = stmt.front(); 814 815 MutatingResolver resolver( *this ); 816 try { 817 callStmt->acceptMutator( resolver ); 818 if ( isCtor ) { 819 function->get_statements()->push_front( callStmt ); 820 } else { 821 // destructor statements should be added at the end 822 function->get_statements()->push_back( callStmt ); 823 } 824 } catch ( SemanticError & error ) { 825 emit( "in ", CodeGen::genType( function->get_functionType(), function->get_name(), false ), ", field ", field->get_name(), " not explicitly ", isCtor ? "constructed" : "destructed", " and no ", isCtor ? "default constructor" : "destructor", " found" ); 826 } 827 } 828 } 829 leaveScope(); 830 } 831 } 832 833 void GenStructMemberCalls::visit( ApplicationExpr * appExpr ) { 744 834 if ( ! checkWarnings( function ) ) return; 745 835 … … 760 850 handleFirstParam( firstParam ); 761 851 } 762 } else if ( fname == "?=?" && isIntrinsicCallExpr( appExpr ) ) {763 // forgive use of intrinsic assignment to construct, since instrinsic constructors764 // codegen as assignment anyway.765 assert( appExpr->get_args().size() == 2 );766 handleFirstParam( appExpr->get_args().front() );767 852 } 768 853 … … 770 855 } 771 856 772 void WarnStructMembers::handleFirstParam( Expression * firstParam ) {857 void GenStructMemberCalls::handleFirstParam( Expression * firstParam ) { 773 858 using namespace std; 774 859 if ( AddressExpr * addrExpr = dynamic_cast< AddressExpr * >( firstParam ) ) { … … 787 872 } 788 873 789 void WarnStructMembers::visit( MemberExpr * memberExpr ) {874 void GenStructMemberCalls::visit( MemberExpr * memberExpr ) { 790 875 if ( ! checkWarnings( function ) ) return; 791 if ( ! isC onstructor( function->get_name() )) return;876 if ( ! isCtor ) return; 792 877 793 878 if ( ApplicationExpr * deref = dynamic_cast< ApplicationExpr * >( memberExpr->get_aggregate() ) ) { … … 797 882 if ( unhandled.count( memberExpr->get_member() ) ) { 798 883 // emit a warning because a member was used before it was constructed 799 warn( "in ", CodeGen::genType( function->get_functionType(), function->get_name(), false ), ", member ", memberExpr->get_member()->get_name(), " used before being constructed");884 usedUninit.insert( memberExpr->get_member() ); 800 885 } 801 886 } … … 805 890 Parent::visit( memberExpr ); 806 891 } 892 893 template< typename Visitor, typename... Params > 894 void error( Visitor & v, const Params &... params ) { 895 v.errors.append( toString( params... ) ); 896 } 897 898 template< typename... Params > 899 void GenStructMemberCalls::emit( const Params &... params ) { 900 // toggle warnings vs. errors here. 901 // warn( params... ); 902 error( *this, params... ); 903 } 904 905 DeclarationWithType * MutatingResolver::mutate( ObjectDecl *objectDecl ) { 906 // add object to the indexer assumes that there will be no name collisions 907 // in generated code. If this changes, add mutate methods for entities with 908 // scope and call {enter,leave}Scope explicitly. 909 objectDecl->accept( indexer ); 910 return objectDecl; 911 } 912 913 Expression* MutatingResolver::mutate( UntypedExpr *untypedExpr ) { 914 return safe_dynamic_cast< ApplicationExpr * >( ResolvExpr::findVoidExpression( untypedExpr, indexer ) ); 915 } 807 916 } // namespace 808 917 } // namespace InitTweak -
src/InitTweak/GenInit.cc
r28307be r3403534 25 25 #include "SynTree/Mutator.h" 26 26 #include "SymTab/Autogen.h" 27 #include "SymTab/Mangler.h" 27 28 #include "GenPoly/PolyMutator.h" 28 29 #include "GenPoly/DeclMutator.h" 30 #include "GenPoly/ScopedSet.h" 29 31 30 32 namespace InitTweak { … … 55 57 class CtorDtor : public GenPoly::PolyMutator { 56 58 public: 59 typedef GenPoly::PolyMutator Parent; 60 using Parent::mutate; 57 61 /// create constructor and destructor statements for object declarations. 58 62 /// the actual call statements will be added in after the resolver has run … … 65 69 // should not traverse into any of these declarations to find objects 66 70 // that need to be constructed or destructed 67 virtual Declaration* mutate( StructDecl *aggregateDecl ) { return aggregateDecl; }71 virtual Declaration* mutate( StructDecl *aggregateDecl ); 68 72 virtual Declaration* mutate( UnionDecl *aggregateDecl ) { return aggregateDecl; } 69 73 virtual Declaration* mutate( EnumDecl *aggregateDecl ) { return aggregateDecl; } … … 74 78 virtual Type * mutate( FunctionType *funcType ) { return funcType; } 75 79 76 protected: 80 virtual CompoundStmt * mutate( CompoundStmt * compoundStmt ); 81 82 private: 83 // set of mangled type names for which a constructor or destructor exists in the current scope. 84 // these types require a ConstructorInit node to be generated, anything else is a POD type and thus 85 // should not have a ConstructorInit generated. 86 87 bool isManaged( ObjectDecl * objDecl ) const ; // determine if object is managed 88 void handleDWT( DeclarationWithType * dwt ); // add type to managed if ctor/dtor 89 GenPoly::ScopedSet< std::string > managedTypes; 90 bool inFunction = false; 77 91 }; 78 92 … … 142 156 143 157 DeclarationWithType* ReturnFixer::mutate( FunctionDecl *functionDecl ) { 144 std::list<DeclarationWithType*> oldReturnVals = returnVals;145 std::string oldFuncName = funcName;158 ValueGuard< std::list<DeclarationWithType*> > oldReturnVals( returnVals ); 159 ValueGuard< std::string > oldFuncName( funcName ); 146 160 147 161 FunctionType * type = functionDecl->get_functionType(); … … 149 163 funcName = functionDecl->get_name(); 150 164 DeclarationWithType * decl = Mutator::mutate( functionDecl ); 151 returnVals = oldReturnVals;152 funcName = oldFuncName;153 165 return decl; 154 166 } … … 197 209 198 210 DeclarationWithType * HoistArrayDimension::mutate( FunctionDecl *functionDecl ) { 199 bool oldInFunc = inFunction;211 ValueGuard< bool > oldInFunc( inFunction ); 200 212 inFunction = true; 201 213 DeclarationWithType * decl = Parent::mutate( functionDecl ); 202 inFunction = oldInFunc;203 214 return decl; 204 215 } … … 209 220 } 210 221 222 bool CtorDtor::isManaged( ObjectDecl * objDecl ) const { 223 Type * type = objDecl->get_type(); 224 while ( ArrayType * at = dynamic_cast< ArrayType * >( type ) ) { 225 type = at->get_base(); 226 } 227 return managedTypes.find( SymTab::Mangler::mangle( type ) ) != managedTypes.end(); 228 } 229 230 void CtorDtor::handleDWT( DeclarationWithType * dwt ) { 231 // if this function is a user-defined constructor or destructor, mark down the type as "managed" 232 if ( ! LinkageSpec::isOverridable( dwt->get_linkage() ) && isCtorDtor( dwt->get_name() ) ) { 233 std::list< DeclarationWithType * > & params = GenPoly::getFunctionType( dwt->get_type() )->get_parameters(); 234 assert( ! params.empty() ); 235 PointerType * type = safe_dynamic_cast< PointerType * >( params.front()->get_type() ); 236 managedTypes.insert( SymTab::Mangler::mangle( type->get_base() ) ); 237 } 238 } 239 211 240 DeclarationWithType * CtorDtor::mutate( ObjectDecl * objDecl ) { 212 // hands off if designated, if @=, or if extern 213 if ( tryConstruct( objDecl ) ) { 241 handleDWT( objDecl ); 242 // hands off if @=, extern, builtin, etc. 243 // if global but initializer is not constexpr, always try to construct, since this is not legal C 244 if ( ( tryConstruct( objDecl ) && isManaged( objDecl ) ) || (! inFunction && ! isConstExpr( objDecl->get_init() ) ) ) { 245 // constructed objects cannot be designated 246 if ( isDesignated( objDecl->get_init() ) ) throw SemanticError( "Cannot include designations in the initializer for a managed Object. If this is really what you want, then initialize with @=.", objDecl ); 247 // xxx - constructed objects should not have initializers nested too deeply 248 214 249 // call into genImplicitCall from Autogen.h to generate calls to ctor/dtor 215 250 // for each constructable object … … 241 276 } 242 277 } 243 return Mutator::mutate( objDecl );278 return Parent::mutate( objDecl ); 244 279 } 245 280 246 281 DeclarationWithType * CtorDtor::mutate( FunctionDecl *functionDecl ) { 282 ValueGuard< bool > oldInFunc = inFunction; 283 inFunction = true; 284 285 handleDWT( functionDecl ); 286 287 managedTypes.beginScope(); 288 // go through assertions and recursively add seen ctor/dtors 289 for ( TypeDecl * tyDecl : functionDecl->get_functionType()->get_forall() ) { 290 for ( DeclarationWithType *& assertion : tyDecl->get_assertions() ) { 291 assertion = assertion->acceptMutator( *this ); 292 } 293 } 247 294 // parameters should not be constructed and destructed, so don't mutate FunctionType 248 295 mutateAll( functionDecl->get_oldDecls(), *this ); 249 296 functionDecl->set_statements( maybeMutate( functionDecl->get_statements(), *this ) ); 297 298 managedTypes.endScope(); 250 299 return functionDecl; 251 300 } 301 302 Declaration* CtorDtor::mutate( StructDecl *aggregateDecl ) { 303 // don't construct members, but need to take note if there is a managed member, 304 // because that means that this type is also managed 305 for ( Declaration * member : aggregateDecl->get_members() ) { 306 if ( ObjectDecl * field = dynamic_cast< ObjectDecl * >( member ) ) { 307 if ( isManaged( field ) ) { 308 managedTypes.insert( SymTab::Mangler::mangle( aggregateDecl ) ); 309 break; 310 } 311 } 312 } 313 return aggregateDecl; 314 } 315 316 CompoundStmt * CtorDtor::mutate( CompoundStmt * compoundStmt ) { 317 managedTypes.beginScope(); 318 CompoundStmt * stmt = Parent::mutate( compoundStmt ); 319 managedTypes.endScope(); 320 return stmt; 321 } 322 252 323 } // namespace InitTweak 253 324 -
src/InitTweak/InitTweak.cc
r28307be r3403534 7 7 #include "SynTree/Attribute.h" 8 8 #include "GenPoly/GenPoly.h" 9 #include "ResolvExpr/typeops.h" 9 10 10 11 namespace InitTweak { … … 79 80 public: 80 81 ExprImpl( Expression * expr ) : arg( expr ) {} 82 83 ~ExprImpl() { delete arg; } 81 84 82 85 virtual std::list< Expression * > next( std::list< Expression * > & indices ) { … … 122 125 123 126 void InitExpander::clearArrayIndices() { 127 deleteAll( indices ); 124 128 indices.clear(); 125 129 } … … 228 232 return ! LinkageSpec::isBuiltin( objDecl->get_linkage() ) && 229 233 (objDecl->get_init() == NULL || 230 ( objDecl->get_init() != NULL && objDecl->get_init()->get_maybeConstructed() )) && 231 ! isDesignated( objDecl->get_init() ) 234 ( objDecl->get_init() != NULL && objDecl->get_init()->get_maybeConstructed() )) 232 235 && objDecl->get_storageClass() != DeclarationNode::Extern; 233 236 } … … 387 390 virtual void visit( ApplicationExpr *applicationExpr ) { isConstExpr = false; } 388 391 virtual void visit( UntypedExpr *untypedExpr ) { isConstExpr = false; } 389 virtual void visit( NameExpr *nameExpr ) { isConstExpr = false; } 390 virtual void visit( CastExpr *castExpr ) { isConstExpr = false; } 392 virtual void visit( NameExpr *nameExpr ) { 393 // xxx - temporary hack, because 0 and 1 really should be constexprs, even though they technically aren't in Cforall today 394 if ( nameExpr->get_name() != "0" && nameExpr->get_name() != "1" ) isConstExpr = false; 395 } 396 // virtual void visit( CastExpr *castExpr ) { isConstExpr = false; } 397 virtual void visit( AddressExpr *addressExpr ) { 398 // address of a variable or member expression is constexpr 399 Expression * arg = addressExpr->get_arg(); 400 if ( ! dynamic_cast< NameExpr * >( arg) && ! dynamic_cast< VariableExpr * >( arg ) && ! dynamic_cast< MemberExpr * >( arg ) && ! dynamic_cast< UntypedMemberExpr * >( arg ) ) isConstExpr = false; 401 } 391 402 virtual void visit( LabelAddressExpr *labAddressExpr ) { isConstExpr = false; } 392 403 virtual void visit( UntypedMemberExpr *memberExpr ) { isConstExpr = false; } 393 404 virtual void visit( MemberExpr *memberExpr ) { isConstExpr = false; } 394 405 virtual void visit( VariableExpr *variableExpr ) { isConstExpr = false; } 395 virtual void visit( ConstantExpr *constantExpr ) { /* bottom out */ }396 406 // these might be okay? 397 407 // virtual void visit( SizeofExpr *sizeofExpr ); … … 436 446 bool isDestructor( const std::string & str ) { return str == "^?{}"; } 437 447 bool isCtorDtor( const std::string & str ) { return isConstructor( str ) || isDestructor( str ); } 448 449 FunctionDecl * isCopyConstructor( Declaration * decl ) { 450 FunctionDecl * function = dynamic_cast< FunctionDecl * >( decl ); 451 if ( ! function ) return 0; 452 if ( ! isConstructor( function->get_name() ) ) return 0; 453 FunctionType * ftype = function->get_functionType(); 454 if ( ftype->get_parameters().size() != 2 ) return 0; 455 456 Type * t1 = ftype->get_parameters().front()->get_type(); 457 Type * t2 = ftype->get_parameters().back()->get_type(); 458 PointerType * ptrType = dynamic_cast< PointerType * > ( t1 ); 459 assert( ptrType ); 460 461 if ( ResolvExpr::typesCompatible( ptrType->get_base(), t2, SymTab::Indexer() ) ) { 462 return function; 463 } else { 464 return 0; 465 } 466 } 438 467 } -
src/InitTweak/InitTweak.h
r28307be r3403534 29 29 bool isDestructor( const std::string & ); 30 30 bool isCtorDtor( const std::string & ); 31 32 FunctionDecl * isCopyConstructor( Declaration * decl ); 31 33 32 34 /// transform Initializer into an argument list that can be passed to a call expression
Note:
See TracChangeset
for help on using the changeset viewer.